(otp-provider.js). They talk
// to MSG91 directly from the browser — our server never sees the raw OTP.
sendOtpBtn.addEventListener('click', function() {
const phoneEl = document.getElementById('f-phone');
const phone = phoneEl.value.trim();
if (phone.length !== 10) {
showErr(phoneEl, 'err-phone');
phoneEl.scrollIntoView({behavior:'smooth', block:'center'});
return;
}
if (typeof sendOtp !== 'function') {
otpBox.classList.add('show');
otpHint.className = 'otp-hint err';
otpHint.textContent = 'OTP service is still loading, please try again in a moment.';
return;
}
const originalLabel = sendOtpBtn.textContent;
sendOtpBtn.disabled = true;
sendOtpBtn.textContent = 'Sending...';
let settled = false;
const timeoutId = setTimeout(function() {
if (settled) return;
settled = true;
otpBox.classList.add('show');
otpHint.className = 'otp-hint err';
otpHint.textContent = 'OTP request timed out — the widget did not respond. Check the browser console for errors, or that the widget is fully configured (Country Restriction, etc.) on MSG91.';
sendOtpBtn.disabled = false;
sendOtpBtn.textContent = originalLabel;
}, 15000);
// MSG91's identifier must include the country code — a bare 10-digit
// number gets accepted by the widget API (success callback fires) but the
// SMS is never actually delivered. Confirmed via the Widget Logs page:
// identifiers like "919943612344" delivered fine, "9943612344" did not.
sendOtp('91' + phone, function() {
if (settled) return;
settled = true;
clearTimeout(timeoutId);
otpBox.classList.add('show');
otpHint.className = 'otp-hint ok';
otpHint.textContent = 'OTP sent to +91' + phone + '.';
otpInput.value = '';
otpInput.focus();
startResendCooldown(30);
}, function(error) {
if (settled) return;
settled = true;
clearTimeout(timeoutId);
otpBox.classList.add('show');
otpHint.className = 'otp-hint err';
otpHint.textContent = (error && error.message) || 'Failed to send OTP.';
sendOtpBtn.disabled = false;
sendOtpBtn.textContent = originalLabel;
});
});
verifyOtpBtn.addEventListener('click', function() {
const otp = otpInput.value.trim();
if (otp.length !== 4) {
otpHint.className = 'otp-hint err';
otpHint.textContent = 'Enter the 4-digit OTP.';
return;
}
verifyOtpBtn.disabled = true;
verifyOtpBtn.textContent = 'Verifying...';
let verifySettled = false;
const verifyTimeoutId = setTimeout(function() {
if (verifySettled) return;
verifySettled = true;
otpHint.className = 'otp-hint err';
otpHint.textContent = 'Verification timed out. Please try again.';
verifyOtpBtn.disabled = false;
verifyOtpBtn.textContent = 'Verify';
}, 15000);
verifyOtp(otp, async function(widgetData) {
if (verifySettled) return;
verifySettled = true;
clearTimeout(verifyTimeoutId);
// Be defensive about which field MSG91 puts the access-token in —
// different widget versions/SDKs have used message / token / access-token.
const accessToken = (widgetData && (widgetData.message || widgetData.token || widgetData['access-token'])) || '';
console.log('verifyOtp widgetData:', widgetData, '→ using accessToken:', accessToken);
const phone = document.getElementById('f-phone').value.trim();
const requestBody = JSON.stringify({ phone: phone, access_token: accessToken });
console.log('About to POST to verify_otp.php with body:', requestBody);
try {
const res = await fetch('verify_otp.php', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: requestBody,
});
console.log('verify_otp.php HTTP status:', res.status, 'final URL:', res.url, 'redirected:', res.redirected);
const result = await res.json();
console.log('verify_otp.php response:', result);
if (result.success) {
otpVerified = true;
otpVerifiedPhone = phone;
otpTokenEl.value = result.token;
otpBox.classList.remove('show');
clearErr(document.getElementById('f-phone'), 'err-otp');
if (otpResendTimer) { clearInterval(otpResendTimer); otpResendTimer = null; }
document.getElementById('verified-phone-display').textContent = '+91 ' + phone;
otpGateCard.style.display = 'none';
otpBanner.classList.add('show');
bookingSteps.classList.add('show');
bookingSteps.scrollIntoView({behavior:'smooth', block:'start'});
} else {
otpHint.className = 'otp-hint err';
// Temporarily show the debug info inline so we can see it without DevTools.
// Remove the "+ debug" part once verify_otp.php's debug block is removed.
const debugSuffix = result.debug ? (' [debug: phone_len=' + result.debug.phone_len + ', token_len=' + result.debug.token_len + ']') : '';
otpHint.textContent = (result.message || 'Verification failed.') + debugSuffix;
verifyOtpBtn.disabled = false;
verifyOtpBtn.textContent = 'Verify';
}
} catch (err) {
otpHint.className = 'otp-hint err';
otpHint.textContent = 'Network error. Please try again.';
verifyOtpBtn.disabled = false;
verifyOtpBtn.textContent = 'Verify';
}
}, function(error) {
if (verifySettled) return;
verifySettled = true;
clearTimeout(verifyTimeoutId);
otpHint.className = 'otp-hint err';
otpHint.textContent = (error && error.message) || 'Incorrect OTP.';
verifyOtpBtn.disabled = false;
verifyOtpBtn.textContent = 'Verify';
});
});
// ── SERVICE SELECTION ──────────────────────────────────────────────────────
function selectSvc(el) {
document.querySelectorAll('.svc-card').forEach(c => c.classList.remove('selected'));
el.classList.add('selected');
document.getElementById('f-svc-id').value = el.dataset.id;
document.getElementById('err-svc').classList.remove('show');
updatePricing();
}
// ── TIME SLOT ──────────────────────────────────────────────────────────────
function selectSlot(el) {
document.querySelectorAll('.slot-card').forEach(c => c.classList.remove('selected'));
el.classList.add('selected');
document.getElementById('f-pref-time').value = el.dataset.slot;
clearErr(null, 'err-pref-time');
}
// ── FORM SUBMIT → VALIDATE → RAZORPAY ─────────────────────────────────────
document.getElementById('bookingForm').addEventListener('submit', async function(e) {
e.preventDefault();
const nameEl = document.getElementById('f-name');
const phoneEl = document.getElementById('f-phone');
const modelEl = document.getElementById('f-veh-model');
const vehNumEl = document.getElementById('f-veh-num');
const dateEl = document.getElementById('f-pref-date');
const svcId = document.getElementById('f-svc-id').value;
const prefTime = document.getElementById('f-pref-time').value;
const branch = branchHidden.value;
const brand = brandHidden.value;
const model = modelEl.value;
// Reset all errors
['err-name','err-phone','err-otp','err-branch','err-brand','err-model','err-vehnum','err-svc','err-pref-date','err-pref-time']
.forEach(id => { const m=document.getElementById(id); if(m) m.classList.remove('show'); });
[nameEl, phoneEl, brandSearch, modelEl, vehNumEl, dateEl].forEach(el => el.classList.remove('field-err'));
let valid=true, first=null;
const fail=(el,id)=>{ showErr(el,id); if(!first) first=el; valid=false; };
if (!nameEl.value.trim()) fail(nameEl, 'err-name');
if (phoneEl.value.length<10) fail(phoneEl, 'err-phone');
else if (!otpVerified || phoneEl.value !== otpVerifiedPhone) fail(phoneEl, 'err-otp');
if (!branch) fail(null, 'err-branch');
if (!brand) fail(brandSearch,'err-brand');
if (!model) fail(modelEl, 'err-model');
if (!vehNumEl.value.trim()) fail(vehNumEl, 'err-vehnum');
if (!svcId) { document.getElementById('err-svc').classList.add('show'); if(!first) first=document.querySelector('.svc-grid'); valid=false; }
if (!dateEl.value) fail(dateEl, 'err-pref-date');
if (!prefTime){ document.getElementById('err-pref-time').classList.add('show'); if(!first) first=document.querySelector('.slot-grid'); valid=false; }
if (!valid) { if(first) first.scrollIntoView({behavior:'smooth',block:'center'}); return; }
const btn = document.getElementById('pay-btn');
btn.disabled = true;
btn.innerHTML = '⏳ Opening payment...';
let order;
try {
const res = await fetch('razorpay_order.php', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({amount:9900}),
});
order = await res.json();
if (!order.id) throw new Error(order.error || 'Order creation failed');
} catch(err) {
alert('Payment error: ' + err.message);
btn.disabled=false; btn.innerHTML='🔒 Pay ₹99 & Confirm Booking';
return;
}
const svcCard = document.querySelector('.svc-card.selected');
new Razorpay({
key: 'rzp_test_RkNFbYMV1YEb9a',
amount: order.amount,
currency: order.currency,
name: '5K Car Care',
description: svcCard ? svcCard.dataset.name : 'Service Booking Advance',
order_id: order.id,
prefill: { name: nameEl.value.trim(), contact: '+91' + phoneEl.value.trim() },
theme: { color: '#E05C1A' },
handler: function(response) {
document.getElementById('rzp_payment_id').value = response.razorpay_payment_id;
document.getElementById('rzp_order_id').value = response.razorpay_order_id;
document.getElementById('rzp_signature').value = response.razorpay_signature;
btn.innerHTML = '⏳ Confirming booking...';
document.getElementById('bookingForm').submit();
},
modal: { ondismiss: function(){ btn.disabled=false; btn.innerHTML='🔒 Pay ₹99 & Confirm Booking'; } }
}).open();
});