document.addEventListener('DOMContentLoaded', function() { // Configuration - replace with your actual Cloudflare worker URL const CLOUDFLARE_WORKER_URL = 'https://oniri.beveillard.workers.dev'; // Only check availability if dates are provided in URL parameters const urlParams = new URLSearchParams(window.location.search); const dateParam = urlParams.get('date'); if (dateParam && dateParam.includes(' to ')) { console.log('📅 Found dates in URL, checking availability:', dateParam); // Show loader before initial availability check showLoader(); checkAvailability(); } else { console.log('📅 No dates in URL, waiting for user to select dates'); // Show a message to select dates instead of default pricing showSelectDatesMessage(); } const dateInput = document.querySelector('[data-input="date"]'); if (dateInput) { if (typeof flatpickr !== 'undefined') { flatpickr(dateInput, { mode: "range", minDate: "today", onChange: function(selectedDates, dateStr) { console.log('Flatpickr onChange:', selectedDates.length, 'dates selected'); // Only update URL and make API call when we have BOTH dates selected if (selectedDates.length === 2) { console.log('✅ Complete date range selected, updating URL and checking availability'); updateDateInUrl(dateStr); updateDateElements(dateStr); } else { console.log('⏳ Waiting for complete date range (only', selectedDates.length, 'date selected)'); // Just update the visual elements without making API calls updateDateElementsOnly(dateStr); } } }); } dateInput.addEventListener('change', function() { updateDateInUrl(this.value); updateDateElements(this.value); }); fillDateFromUrl(); updateDateElementsFromUrl(); } else { updateDateElementsFromUrl(); } /** * Get apartment ID from the page * @returns {string} Apartment ID */ function getApartmentId() { // Try multiple methods to find the apartment ID // Method 1: data-booking-id attribute const bookingElement = document.querySelector('[data-booking-id]'); if (bookingElement) { return bookingElement.getAttribute('data-booking-id'); } // Method 2: data-apartment-id attribute const apartmentElement = document.querySelector('[data-apartment-id]'); if (apartmentElement) { return apartmentElement.getAttribute('data-apartment-id'); } // Method 3: Check if ID is in URL path const pathSegments = window.location.pathname.split('/'); const potentialId = pathSegments[pathSegments.length - 1]; if (/^\d+$/.test(potentialId)) { return potentialId; } // Fallback to default ID if nothing found console.warn('No apartment ID found on page, using default ID'); return "209948"; } function calculateNights(startDateStr, endDateStr) { const startDate = new Date(startDateStr); const endDate = new Date(endDateStr); const diffTime = Math.abs(endDate - startDate); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } function checkAvailability() { const params = new URLSearchParams(window.location.search); const dateParam = params.get('date'); const guestParam = params.get('guest') || 1; // Only proceed if we have valid date parameters if (!dateParam || !dateParam.includes(' to ')) { console.log('❌ No valid dates found, cannot check availability'); showSelectDatesMessage(); return; } const dateParts = dateParam.split(' to '); if (dateParts.length !== 2) { console.log('❌ Invalid date format, cannot check availability'); showSelectDatesMessage(); return; } const checkinDate = dateParts[0].replace(/\+/g, ' ').trim(); const checkoutDate = dateParts[1].replace(/\+/g, ' ').trim(); const calculatedNights = calculateNights(checkinDate, checkoutDate); console.log('✅ Valid dates found - Checking availability:'); console.log('📅 Dates:', checkinDate, 'to', checkoutDate); console.log('🌙 Nights:', calculatedNights); // Get the apartment ID dynamically const apartmentId = getApartmentId(); console.log('Apartment ID:', apartmentId); // Use our new worker endpoint structure with dynamic ID const proxyUrl = `${CLOUDFLARE_WORKER_URL}/availability/${apartmentId}`; const requestData = { checkin_date: checkinDate, checkout_date: checkoutDate, nb_adults: parseInt(guestParam), nb_children: 0, nb_babies: 0 }; fetch(proxyUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }) .then(response => response.json()) .then(data => { // Hide loader when API call completes successfully hideLoader(); if (data && data.rentals && data.rentals.length > 0) { const rentalInfo = data.rentals[0]; console.log('🏠 Rental info received:', rentalInfo); console.log('📍 Is available:', rentalInfo.is_available); console.log('💰 Pricing array:', rentalInfo.pricing); // Check if apartment is actually available if (rentalInfo.is_available !== false) { // Hide not-available elements when apartment is available document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'none'; }); } else { console.log('❌ Apartment marked as not available in API response'); // Show not-available elements document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'block'; }); return; } if (rentalInfo.pricing && rentalInfo.pricing.length > 0) { // IMPORTANT: API's avg_per_night is not reliable - use total_amount as source of truth // Calculate the actual price per night with precision to ensure exact math const totalAmount = rentalInfo.pricing[0].total_amount; const nights = calculatedNights; if (!totalAmount || totalAmount <= 0) { console.log('❌ Invalid total_amount in pricing:', totalAmount); // Show not-available elements if pricing is invalid document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'block'; }); return; } const actualPricePerNight = totalAmount / nights; // Keep decimals for exact calculation console.log('=== PRICE CALCULATION METHOD ==='); console.log('API avg_per_night (not reliable):', rentalInfo.pricing[0].avg_per_night); console.log('API total_amount (source of truth):', totalAmount); console.log('Calculated nights:', nights); console.log('Actual price per night (with decimals):', actualPricePerNight, '=', totalAmount, '/', nights); console.log('Formatted price display:', actualPricePerNight.toFixed(2), '€'); console.log('================================'); // Extract fees from the API response let taxesFees = 0; let cleaningFees = 0; if (rentalInfo.pricing[0].included_fees && rentalInfo.pricing[0].included_fees.length > 0) { // Find occupancy tax (taxe de séjour) const taxesFeeWrapper = rentalInfo.pricing[0].included_fees.find(fee => fee.name && (fee.name.fr?.toLowerCase().includes('taxe') || fee.name.en?.toLowerCase().includes('tax')) ); if (taxesFeeWrapper) { taxesFees = parseFloat(taxesFeeWrapper.price) || 0; } // Find cleaning fees (frais de ménage) const cleaningFeeWrapper = rentalInfo.pricing[0].included_fees.find(fee => fee.name && (fee.name.fr?.toLowerCase().includes('ménage') || fee.name.en?.toLowerCase().includes('cleaning')) ); if (cleaningFeeWrapper) { cleaningFees = parseFloat(cleaningFeeWrapper.price) || 0; } } // Calculate the base price using the actual price per night (derived from total) const basePrice = actualPricePerNight * nights; // The total from API is our base amount, and we need to ADD fees to get final total const finalTotalRaw = totalAmount + taxesFees + cleaningFees; // Dynamic rounding logic - try different rounding methods to match API expectations // Use more precise rounding to avoid floating point issues const roundedUp = Math.ceil(Math.round(finalTotalRaw * 100) / 100); const roundedDown = Math.floor(Math.round(finalTotalRaw * 100) / 100); const roundedNearest = Math.round(Math.round(finalTotalRaw * 100) / 100); // For now, use ceil (round up) but we'll make this configurable const finalTotal = roundedUp; console.log('🔄 Rounding options:', { raw: finalTotalRaw, ceil: roundedUp, floor: roundedDown, round: roundedNearest, selected: finalTotal }); console.log('=== FINAL CALCULATIONS ==='); console.log('Actual price per night:', actualPricePerNight); console.log('Number of nights:', nights); console.log('Base price (actual price × nights):', basePrice); console.log('Total amount from API (base):', totalAmount); console.log('Taxes fees:', taxesFees); console.log('Cleaning fees:', cleaningFees); console.log('Final total (raw calculation):', finalTotalRaw, '=', totalAmount, '+', taxesFees, '+', cleaningFees); console.log('Final total (rounded for API):', finalTotal); console.log('=== BOOKING API PRICE ==='); console.log('Sending to booking API:', finalTotal, '(rounded total with fees)'); console.log('API expects rounded UP amount:', finalTotalRaw, '→', finalTotal); console.log('=== MATH VERIFICATION ==='); console.log(`${actualPricePerNight.toFixed(2)} × ${nights} = ${(actualPricePerNight * nights).toFixed(2)} (should equal API total: ${totalAmount})`); console.log(`Perfect match: ${actualPricePerNight * nights === totalAmount ? '✅ EXACT MATCH' : '❌ MISMATCH'}`); console.log(`Final customer total: ${totalAmount} + ${taxesFees + cleaningFees} = ${finalTotal}€`); console.log('=========================='); // Format price with appropriate decimals (2 decimals or just 1 if needed) const formattedPrice = actualPricePerNight.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }); // Update price displays with the actual price per night document.querySelectorAll('[data-price]').forEach(el => { if (el.tagName === 'INPUT') { el.value = actualPricePerNight; } else { el.textContent = formattedPrice; } el.setAttribute('data-price', actualPricePerNight); }); // Mettre à jour les nuits avec notre valeur calculée document.querySelectorAll('[data-nombre-nuit]').forEach(el => { if (el.tagName === 'INPUT') { el.value = nights; } else { el.textContent = nights; } el.setAttribute('data-nombre-nuit', nights); }); // ✅ Mettre à jour le sous-total et le total avec les calculs corrects updateSubtotalAndTotalPrices(basePrice, totalAmount, finalTotal, taxesFees, cleaningFees); } else { // No pricing data found - show not-available console.log('❌ No pricing data found in rental info'); document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'block'; }); } } else { // No rental data found - show not-available console.log('❌ No rental data found for these dates'); document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'block'; }); } }) .catch(error => { // Hide loader on error hideLoader(); console.error('❌ Error during availability check:', error); // Show not-available elements on error document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'block'; }); }); } function updateSubtotalAndTotalPrices(basePrice, subtotalAmount, finalTotal, taxesFees, cleaningFees) { // Update the fee elements const taxesElement = document.querySelector('[data-taxes-fees]'); const cleaningElement = document.querySelector('[data-cleaning-fees]'); if (taxesElement) { if (taxesElement.tagName === 'INPUT') { taxesElement.value = taxesFees; } else { taxesElement.textContent = taxesFees; } taxesElement.setAttribute('data-taxes-fees', taxesFees); } if (cleaningElement) { if (cleaningElement.tagName === 'INPUT') { cleaningElement.value = cleaningFees; } else { cleaningElement.textContent = cleaningFees } cleaningElement.setAttribute('data-cleaning-fees', cleaningFees); } // Store the subtotal amount for booking API // The booking API expects the base price (subtotalAmount), and it will add fees const bookingPriceElement = document.getElementById('price-booking'); if (bookingPriceElement) { if (bookingPriceElement.tagName === 'INPUT') { bookingPriceElement.value = finalTotal; } else { bookingPriceElement.textContent = finalTotal + " €"; } // Store the final total for the booking API (API expects total with fees) bookingPriceElement.setAttribute('data-price-booking', finalTotal); bookingPriceElement.setAttribute('data-total-amount', finalTotal); // Also store the raw total_amount from API (without fees added by us) bookingPriceElement.setAttribute('data-total-amount-base', subtotalAmount); } // Subtotal is the amount from API (before fees) const subtotal = subtotalAmount; // Total is the final amount (subtotal + fees) const total = finalTotal; // Mettre à jour tous les éléments [data-subtotal-price] document.querySelectorAll('[data-subtotal-price]').forEach(el => { if (el.tagName === 'INPUT') { el.value = subtotal; } else { el.textContent = subtotal + " €"; } el.setAttribute('data-subtotal-price', subtotal); }); // Mettre à jour tous les éléments [data-total-price] document.querySelectorAll('[data-total-price]').forEach(el => { if (el.tagName === 'INPUT') { el.value = total; } else { el.textContent = total + " €"; } el.setAttribute('data-total-price', total); }); } function updateDateInUrl(dateValue) { const url = new URL(window.location.href); const params = new URLSearchParams(url.search); params.delete('date'); if (dateValue && dateValue.trim() !== '') { params.set('date', dateValue); } url.search = params.toString(); window.history.replaceState({}, '', url); // Show loader before making API call console.log('🔄 Starting availability check...'); showLoader(); checkAvailability(); } function fillDateFromUrl() { const params = new URLSearchParams(window.location.search); const dateParam = params.get('date'); if (dateParam && dateInput) { dateInput.value = dateParam; if (dateInput._flatpickr) { const dates = dateParam.split(' to '); if (dates.length === 2) { const startDate = new Date(dates[0]); const endDate = new Date(dates[1]); dateInput._flatpickr.setDate([startDate, endDate]); } } } } function updateDateElements(dateValue) { if (dateValue) { const dateParts = dateValue.split(' to '); if (dateParts.length === 2) { const dateFrom = dateParts[0].replace(/\+/g, ' ').trim(); const dateTo = dateParts[1].replace(/\+/g, ' ').trim(); const nights = calculateNights(dateFrom, dateTo); updateDateElementsWithValues(dateFrom, dateTo, nights); } } } // Update visual elements only (no API calls) function updateDateElementsOnly(dateValue) { if (dateValue) { const dateParts = dateValue.split(' to '); if (dateParts.length === 2) { const dateFrom = dateParts[0].replace(/\+/g, ' ').trim(); const dateTo = dateParts[1].replace(/\+/g, ' ').trim(); const nights = calculateNights(dateFrom, dateTo); // Only update visual date displays, don't trigger availability check console.log('📅 Updating date displays only (no API call)'); updateDateElementsWithValues(dateFrom, dateTo, nights); } else if (dateParts.length === 1 && dateParts[0].trim()) { // Single date selected, show only the start date const dateFrom = dateParts[0].replace(/\+/g, ' ').trim(); console.log('📅 Single date selected:', dateFrom); // Update only the from date, clear the to date const dateFromElements = document.querySelectorAll('[js-form-usequery=\"date-from\"]'); const dateToElements = document.querySelectorAll('[js-form-usequery=\"date-to\"]'); dateFromElements.forEach(el => el.tagName === 'INPUT' ? el.value = dateFrom : el.textContent = dateFrom); dateToElements.forEach(el => el.tagName === 'INPUT' ? el.value = '' : el.textContent = 'Select end date'); } } } function updateDateElementsFromUrl() { const params = new URLSearchParams(window.location.search); const dateParam = params.get('date'); if (dateParam) { updateDateElements(dateParam); } } function updateDateElementsWithValues(dateFrom, dateTo, nights) { const dateFromElements = document.querySelectorAll('[js-form-usequery="date-from"]'); const dateToElements = document.querySelectorAll('[js-form-usequery="date-to"]'); const nightsElements = document.querySelectorAll('[data-nombre-nuit]'); // Format dates to French format const formatDateToFrench = (dateStr) => { const date = new Date(dateStr + 'T12:00:00'); // Add noon to avoid timezone issues return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }); }; const frenchDateFrom = formatDateToFrench(dateFrom); const frenchDateTo = formatDateToFrench(dateTo); console.log('📅 Formatting dates to French:', { original_from: dateFrom, original_to: dateTo, french_from: frenchDateFrom, french_to: frenchDateTo }); dateFromElements.forEach(el => el.tagName === 'INPUT' ? el.value = frenchDateFrom : el.textContent = frenchDateFrom); dateToElements.forEach(el => el.tagName === 'INPUT' ? el.value = frenchDateTo : el.textContent = frenchDateTo); nightsElements.forEach(el => el.tagName === 'INPUT' ? el.value = nights : el.textContent = nights); nightsElements.forEach(el => el.setAttribute('data-nombre-nuit', nights)); // Prices will be updated when availability is checked } // Booking functionality const bookButton = document.getElementById('cta-book'); if (bookButton) { bookButton.addEventListener('click', (event) => { event.preventDefault(); console.log('Bouton de réservation cliqué, lancement de la requête...'); makeBooking(); }); console.log('Écouteur d\'événements ajouté au bouton de réservation'); } else { console.warn('Le bouton avec l\'ID "cta-book" n\'a pas été trouvé dans la page'); } /** * Function to make a booking */ function makeBooking() { // Show the loader const loader = document.getElementById('loader'); if (loader) { loader.style.display = 'flex'; } // Récupérer les dates et informations depuis l'URL ou les éléments de la page const params = new URLSearchParams(window.location.search); // Dates const dateParam = params.get('date'); let checkinDate = "2025-10-24"; let checkoutDate = "2025-10-29"; if (dateParam) { const dateParts = dateParam.split(' to '); if (dateParts.length === 2) { checkinDate = dateParts[0].replace(/\+/g, ' ').trim(); checkoutDate = dateParts[1].replace(/\+/g, ' ').trim(); } } // Nombre d'invités const guestParam = params.get('guest'); const nbAdults = guestParam ? parseInt(guestParam) : 2; // Récupérer la valeur de price-booking depuis l'élément HTML // IMPORTANT: Use the total price (with fees) for the booking API let priceBooking = '10'; // Valeur par défaut const priceBookingElement = document.getElementById('price-booking'); if (priceBookingElement) { // PRIORITY 1: Use data-price-booking attribute (most reliable) const dataBookingPrice = priceBookingElement.getAttribute('data-price-booking'); const totalAmount = priceBookingElement.getAttribute('data-total-amount'); if (dataBookingPrice && !isNaN(parseFloat(dataBookingPrice))) { priceBooking = parseFloat(dataBookingPrice).toString(); console.log('🏷️ Using data-price-booking attribute:', priceBooking); console.log('📊 This includes all fees as expected by the API'); } else if (totalAmount && !isNaN(parseFloat(totalAmount))) { // PRIORITY 2: Use data-total-amount attribute priceBooking = parseFloat(totalAmount).toString(); console.log('🏷️ Using data-total-amount attribute:', priceBooking); } else { // PRIORITY 3: Robust fallback with improved mobile compatibility console.log('⚠️ Using fallback price extraction (mobile-safe)'); // Get raw text content let rawPrice = priceBookingElement.textContent || priceBookingElement.value || priceBookingElement.innerText || '10'; console.log('Raw price text:', rawPrice); // Mobile-safe price extraction: more conservative approach // Remove all non-numeric characters except dots and commas let cleanPrice = rawPrice.replace(/[^\d.,]/g, ''); console.log('After removing non-numeric:', cleanPrice); // Handle European format (comma as decimal separator) if (cleanPrice.includes(',') && cleanPrice.includes('.')) { // Format like "1.234,56" - comma is decimal separator cleanPrice = cleanPrice.replace(/\./g, '').replace(',', '.'); } else if (cleanPrice.includes(',') && !cleanPrice.includes('.')) { // Format like "1234,56" - comma is decimal separator cleanPrice = cleanPrice.replace(',', '.'); } // If only dots, assume US format (dots as decimal separator) console.log('After decimal handling:', cleanPrice); // Parse and validate const parsedPrice = parseFloat(cleanPrice); if (!isNaN(parsedPrice) && parsedPrice > 0) { priceBooking = parsedPrice.toString(); console.log('✅ Successfully parsed fallback price:', priceBooking); } else { console.warn('❌ Failed to parse price, using default'); priceBooking = '10'; } } console.log('💰 Final booking price to send to API:', priceBooking); } else { console.warn('Élément price-booking non trouvé, utilisation de la valeur par défaut'); } // Récupérer les infos client depuis le formulaire s'il existe let emailAddress = ''; let firstName = ''; let lastName = ''; let phoneNumber = ''; // Trouver les champs de formulaire client s'ils existent const emailInput = document.querySelector('[data-input="email"]'); const firstNameInput = document.querySelector('[data-input="firstName"]'); const lastNameInput = document.querySelector('[data-input="lastName"]'); const phoneInput = document.querySelector('[data-input="phone"]'); if (emailInput) emailAddress = emailInput.value; if (firstNameInput) firstName = firstNameInput.value; if (lastNameInput) lastName = lastNameInput.value; if (phoneInput) phoneNumber = phoneInput.value; // Fallback values if form fields are empty emailAddress = emailAddress || "client@example.com"; firstName = firstName || "Invité"; lastName = lastName || "Anonyme"; phoneNumber = phoneNumber || "+33 0 00 00 00 00"; // Get apartment ID dynamically const apartmentId = getApartmentId(); const appartTestMathias = 209948; if(apartmentId === appartTestMathias) { priceBooking = 6; } // Get the stored rounding options from the booking element const rawPrice = parseFloat(priceBooking); // Get the original total_amount from API (without fees) for 3rd test const totalAmountOnly = priceBookingElement?.getAttribute('data-total-amount-base') || null; // Approach: try 3 prices - rounded up, rounded down, and raw total_amount const roundedUp = Math.ceil(rawPrice); const roundedDown = Math.floor(rawPrice); let pricesToTry = []; if (roundedUp === roundedDown) { // If rounding gives same result, try exact and ±1 pricesToTry = [rawPrice, rawPrice - 1]; } else { // Try rounded up and down pricesToTry = [roundedUp, roundedDown]; } // Add the raw total_amount (without fees) as 3rd option if available if (totalAmountOnly && !pricesToTry.includes(parseFloat(totalAmountOnly))) { pricesToTry.push(parseFloat(totalAmountOnly)); } const uniquePrices = [...new Set(pricesToTry)].filter(price => price > 0); console.log('🎯 Trying prices in parallel:', uniquePrices); console.log('📊 Price breakdown:', { 'calculated_with_fees': rawPrice, 'total_amount_only': totalAmountOnly, 'trying': uniquePrices }); // Mobile debugging information console.log('🔍 MOBILE DEBUG INFO:'); console.log('User Agent:', navigator.userAgent); console.log('Is Mobile:', /Mobi|Android/i.test(navigator.userAgent)); console.log('Screen Width:', window.screen.width); console.log('Viewport Width:', window.innerWidth); console.log('Price Element Info:', { 'element_exists': !!priceBookingElement, 'data_price_booking': priceBookingElement?.getAttribute('data-price-booking'), 'data_total_amount': priceBookingElement?.getAttribute('data-total-amount'), 'text_content': priceBookingElement?.textContent, 'inner_text': priceBookingElement?.innerText, 'value': priceBookingElement?.value }); // Create booking data template const baseBookingData = { checkin_date: checkinDate, checkout_date: checkoutDate, nb_adults: nbAdults, nb_children: 0, nb_babies: 0, rate_name: "tarif-flexible", customer: { email_address: emailAddress, first_name: firstName, last_name: lastName, phone_number: phoneNumber } }; // Nouvelle URL de l'endpoint booking avec l'ID dynamique const bookingUrl = `${CLOUDFLARE_WORKER_URL}/booking/${apartmentId}`; // Create parallel requests with different prices const bookingPromises = uniquePrices.map(price => { const bookingData = { ...baseBookingData, price: price }; console.log(`🚀 Attempting booking with price: ${price}€`); return fetch(bookingUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(bookingData) }) .then(response => { if (response.ok) { console.log(`✅ SUCCESS with price: ${price}€`); return response.json().then(data => ({ success: true, data, price })); } else { console.log(`❌ FAILED with price: ${price}€`); return response.json().then(errorData => ({ success: false, error: errorData, price, status: response.status })); } }) .catch(error => { console.log(`❌ ERROR with price: ${price}€`, error); return { success: false, error: error.message, price }; }); }); // Wait for all requests and use the first successful one Promise.allSettled(bookingPromises) .then(results => { // Find the first successful result const successfulResult = results.find(result => result.status === 'fulfilled' && result.value.success ); if (successfulResult) { const { data, price } = successfulResult.value; console.log(`🎉 Booking successful with price: ${price}€`); console.log('=== DÉTAILS DE LA RÉSERVATION ==='); console.log(data); return data; } else { // All failed - show details console.log('❌ All booking attempts failed:'); results.forEach(result => { if (result.status === 'fulfilled') { console.log(`Price ${result.value.price}€:`, result.value.error); } else { console.log('Request error:', result.reason); } }); throw new Error('All booking attempts failed with different prices'); } }) .then(data => { // Hide the loader if (loader) { loader.style.display = 'none'; } // Afficher les résultats dans la console console.log('=== DÉTAILS DE LA RÉSERVATION ==='); console.log(data); // Créer l'URL avec les paramètres client if (data.payment_url) { // Créer l'URL avec les paramètres const paymentUrlObj = new URL(data.payment_url); // Ajouter les paramètres client sans le numéro de téléphone if (data.customer) { // Utilisation de set au lieu de append pour éviter le double encodage paymentUrlObj.searchParams.set('email', data.customer.email_address); paymentUrlObj.searchParams.set('first_name', data.customer.first_name); paymentUrlObj.searchParams.set('last_name', data.customer.last_name); // Suppression du paramètre phone - ne pas l'ajouter } // Ajouter le paramètre price-booking avec la valeur nettoyée paymentUrlObj.searchParams.set('price-booking', priceBooking); // Construction manuelle de l'URL pour éviter le double encodage let finalUrl = paymentUrlObj.origin + paymentUrlObj.pathname; let params = []; for (const [key, value] of paymentUrlObj.searchParams.entries()) { params.push(`${key}=${value}`); } if (params.length > 0) { finalUrl += '?' + params.join('&'); } // Afficher l'URL de redirection console.log('Redirection vers:', finalUrl); // Stocker les informations de réservation dans sessionStorage sessionStorage.setItem('lastBooking', JSON.stringify(data)); // Rediriger vers l'URL de paiement (compatible avec tous les navigateurs) setTimeout(() => { window.location.href = finalUrl; }, 200); // Délai pour voir les logs avant redirection } else { console.warn('Aucune URL de paiement trouvée dans la réponse'); } return data; }) .catch(error => { // Hide the loader in case of error if (loader) { loader.style.display = 'none'; } console.error('❌ ERREUR DE RÉSERVATION'); console.error('Message:', error.message); // Afficher une alerte à l'utilisateur alert(`Erreur lors de la réservation: ${error.message}. Veuillez réessayer.`); }); } // Loader management functions function showLoader() { const loader = document.getElementById('loader'); if (loader) { loader.style.display = 'flex'; console.log('💫 Showing loader'); } // Hide the waiting-api element during API requests const waitingApiElement = document.getElementById('waiting-api'); if (waitingApiElement) { waitingApiElement.style.display = 'none'; console.log('🔒 Hiding waiting-api element during API request'); } } function hideLoader() { const loader = document.getElementById('loader'); if (loader) { loader.style.display = 'none'; console.log('✅ Hiding loader'); } // Show the waiting-api element when API request completes const waitingApiElement = document.getElementById('waiting-api'); if (waitingApiElement) { waitingApiElement.style.display = 'block'; console.log('🔓 Showing waiting-api element after API request'); } } function showSelectDatesMessage() { // Hide not-available elements when no dates are selected document.querySelectorAll('.not-available').forEach(el => { el.style.display = 'none'; }); // Show a message encouraging users to select dates const priceElements = document.querySelectorAll('[data-price]'); priceElements.forEach(el => { if (el.tagName === 'INPUT') { el.value = ''; } else { el.textContent = 'Select dates to see pricing'; } }); // Clear other pricing displays document.querySelectorAll('[data-subtotal-price]').forEach(el => { if (el.tagName === 'INPUT') { el.value = ''; } else { el.textContent = 'Select dates'; } }); document.querySelectorAll('[data-total-price]').forEach(el => { if (el.tagName === 'INPUT') { el.value = ''; } else { el.textContent = 'Select dates'; } }); console.log('💡 Showing select dates message - no pricing displayed'); } });