Book a Service

Fast, easy, secure. Pick a service and pay online.

1 Service
2 Schedule
3 Your Info
4 Payment

Choose a Service

Pick a Date & Time

Date

Available Times

Your Information

Review & Pay

🔧 Demo Mode — FluidPay Tokenizer Placeholder In production, this card form is replaced by FluidPay's secure iframe via tokenizer.js. Card data never touches your server. See the technical spec for integration details.
💳 Payment PCI SAQ-A
🔒

FluidPay Secure Card Form
In production, the FluidPay Tokenizer renders an iframe here. Card data is captured directly by FluidPay's servers — never stored or seen by this website.

Secured by FluidPay · PCI DSS Compliant

You're Booked!

We've received your appointment and payment. A confirmation has been sent to your email.

in document.getElementById('demo-card-form').style.display = 'none'; window._tokenizer = new Tokenizer({ url: FLUIDPAY_SANDBOX_URL, // switch to 'https://app.fluidpay.com' for production apikey: FLUIDPAY_PUBLIC_KEY, container: '#fluidpay-tokenizer-container', submission: function(resp) { if (resp.status === 'success') { sendTokenToBackend(resp.token); } else if (resp.status === 'validation') { showPaymentError('Please check your card details and try again.'); } else { showPaymentError('Payment error: ' + (resp.msg || 'Please try again.')); } }, settings: { styles: { input: { 'border': '2px solid #e2e8f0', 'border-radius': '7px', 'padding': '11px 14px', 'font-size': '15px', 'color': '#1a1a2e', }, 'input:focus': { 'border-color': '#1a1a2e', 'outline': 'none', } } } }); } // --- PAYMENT SUBMISSION ------------------------------------------------------ function submitPayment() { const payError = document.getElementById('payment-error'); payError.style.display = 'none'; if (FLUIDPAY_ENABLED) { // Production: trigger tokenizer — callback handles the rest setPayBtnLoading(true); window._tokenizer.submit(); } else { // Demo mode: simulate the flow const card = document.getElementById('demo-card').value.replace(/\s/g, ''); const exp = document.getElementById('demo-exp').value; const cvv = document.getElementById('demo-cvv').value; if (!card || card.length < 13 || !exp || !cvv) { payError.style.display = 'block'; payError.textContent = 'Please enter your card details.'; return; } setPayBtnLoading(true); // Simulate network delay (replace with real backend call) setTimeout(() => { setPayBtnLoading(false); showConfirmation(); }, 1800); } } function sendTokenToBackend(token) { // --- BACKEND INTEGRATION POINT ------------------------------------------- // POST to your backend with the token + booking details // Your backend then calls FluidPay's transaction API with your SECRET key // // Example payload your backend receives: // { // token: "fp_tok_xxxxx", // FluidPay token (2-min TTL) // amount: 4900, // cents // serviceId: "oil-change", // bookingDate: "2026-04-15", // bookingTime: "10:00 AM", // customer: { firstName, lastName, phone, email } // } // // Backend then calls: // POST https://sandbox.fluidpay.com/api/transaction // Authorization: YOUR_SECRET_KEY // { "type": "sale", "amount": 4900, "payment_method": { "token": "" }, ... } fetch('/api/book-and-charge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: token, amount: state.selectedService.price, service: state.selectedService, bookingDate: state.bookingDate, bookingTime: state.bookingTime, customer: state.customer, }) }) .then(res => res.json()) .then(data => { setPayBtnLoading(false); if (data.success) { showConfirmation(); } else { showPaymentError(data.message || 'Payment failed. Please try again.'); } }) .catch(err => { setPayBtnLoading(false); showPaymentError('Network error. Please try again.'); }); } function showConfirmation() { // Update step indicators for (let i = 1; i <= 4; i++) { document.getElementById(`step-indicator-${i}`).className = 'step done'; } // Render confirmation details const details = document.getElementById('confirmation-details'); const svc = state.selectedService; const confirmId = 'BK' + Math.random().toString(36).substr(2, 8).toUpperCase(); details.innerHTML = `
Confirmation # ${confirmId}
Service ${svc.name}
Date & Time ${formatDisplayDate(state.bookingDate)} @ ${state.bookingTime}
Name ${state.customer.firstName} ${state.customer.lastName}
${state.customer.vehicle ? `
Vehicle ${state.customer.vehicle}
` : ''}
Amount Charged $${(svc.price / 100).toFixed(2)}
`; document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); document.getElementById('panel-5').classList.add('active'); document.querySelector('.booking-card').scrollIntoView({ behavior: 'smooth', block: 'start' }); } function showPaymentError(msg) { const payError = document.getElementById('payment-error'); payError.textContent = msg; payError.style.display = 'block'; } function setPayBtnLoading(loading) { const btn = document.getElementById('pay-btn'); const label = document.getElementById('pay-btn-label'); btn.disabled = loading; if (loading) { label.innerHTML = ' Processing...'; } else { label.innerHTML = 'Book & Pay'; } } function resetBooking() { state = { selectedService: null, bookingDate: null, bookingTime: null, customer: {}, currentStep: 1 }; document.getElementById('booking-date').value = ''; document.getElementById('first-name').value = ''; document.getElementById('last-name').value = ''; document.getElementById('phone').value = ''; document.getElementById('email').value = ''; document.getElementById('vehicle').value = ''; document.getElementById('demo-card').value = ''; document.getElementById('demo-exp').value = ''; document.getElementById('demo-cvv').value = ''; renderServices(); renderTimeSlots(); for (let i = 1; i <= 4; i++) { const ind = document.getElementById(`step-indicator-${i}`); ind.className = 'step'; if (i === 1) ind.classList.add('active'); } document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); document.getElementById('panel-1').classList.add('active'); document.querySelector('.booking-card').scrollIntoView({ behavior: 'smooth', block: 'start' }); } // --- FORMATTING HELPERS ----------------------------------------------------- function formatCard(input) { let v = input.value.replace(/\D/g, '').substring(0, 16); input.value = v.replace(/(.{4})/g, '$1 ').trim(); } function formatExp(input) { let v = input.value.replace(/\D/g, '').substring(0, 4); if (v.length >= 2) v = v.substring(0, 2) + '/' + v.substring(2); input.value = v; } // --- KICK IT OFF ------------------------------------------------------------ init();