mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-06-30 17:50:31 -05:00
Add Square deposit payment integration
- Square Web Payments SDK card element in booking form - Delayed-capture hold ($100) on booking submit — not charged until confirmed - Live payment status field: Verifying card → Authorizing → Confirmed w/ hold ID - Admin: Capture / Void / Refund actions for each booking - square_payment_id returned in API response for frontend confirmation display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+97
-21
@@ -811,7 +811,17 @@
|
||||
</select>
|
||||
<input type="date" name="date" required />
|
||||
<textarea name="message" placeholder="Anything else we should know? (optional)"></textarea>
|
||||
<button type="submit">Send Booking Request</button>
|
||||
|
||||
<!-- Square deposit card -->
|
||||
<div style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:1rem;margin-top:0.25rem">
|
||||
<p style="font-size:0.78rem;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:rgba(249,115,22,0.8);margin-bottom:0.5rem">Refundable Deposit — $100</p>
|
||||
<p style="font-size:0.8rem;color:rgba(255,255,255,0.5);margin-bottom:0.85rem;line-height:1.5">A $100 hold will be placed on your card — <strong style="color:rgba(255,255,255,0.75)">not charged</strong> until your booking is confirmed. Released in full if declined or at return.</p>
|
||||
<div id="card-container" style="min-height:44px"></div>
|
||||
<p id="card-errors" style="color:#f87171;font-size:0.78rem;margin-top:0.4rem;display:none"></p>
|
||||
<div id="deposit-status" style="display:none;margin-top:0.6rem;font-size:0.82rem;border-radius:6px;padding:0.5rem 0.75rem;line-height:1.5"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn">Submit Booking Request</button>
|
||||
<div class="form-msg" id="formMsg"></div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -834,6 +844,7 @@
|
||||
<p style="margin-top:0.5rem;">Polaris Slingshot® is a registered trademark of Polaris Inc. We are an independent rental operator.</p>
|
||||
</footer>
|
||||
|
||||
<script src="https://web.squarecdn.com/v1/square.js"></script>
|
||||
<script>
|
||||
// ── Mobile nav ───────────────────────────────────────────────────────────────
|
||||
const navToggle = document.getElementById('navToggle');
|
||||
@@ -973,52 +984,117 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── Square Web Payments ───────────────────────────────────────────────────────
|
||||
let squareCard = null;
|
||||
async function initSquare() {
|
||||
if (!window.Square) return;
|
||||
const cardEl = document.getElementById('card-container');
|
||||
if (cardEl) cardEl.style.display = '';
|
||||
try {
|
||||
const payments = Square.payments('sq0idp-YSM7BU9IVyOWSzpeP-0nzQ', 'L8GZYHYKE95CE');
|
||||
squareCard = await payments.card({
|
||||
style: {
|
||||
'.input-container': { borderColor: 'rgba(255,255,255,0.12)', borderRadius: '6px' },
|
||||
'.input-container.is-focus': { borderColor: '#f97316' },
|
||||
'.input-container.is-error': { borderColor: '#ef4444' },
|
||||
'input': { color: '#ffffff', fontSize: '15px' },
|
||||
'.message-text': { color: '#f87171' },
|
||||
}
|
||||
});
|
||||
await squareCard.attach('#card-container');
|
||||
} catch (e) {
|
||||
console.warn('Square init error:', e);
|
||||
}
|
||||
}
|
||||
initSquare();
|
||||
|
||||
// ── Booking Form ─────────────────────────────────────────────────────────────
|
||||
document.getElementById('bookingForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const form = this;
|
||||
const msg = document.getElementById('formMsg');
|
||||
const btn = form.querySelector('button[type="submit"]');
|
||||
btn.textContent = 'Sending...';
|
||||
const form = this;
|
||||
const msg = document.getElementById('formMsg');
|
||||
const btn = document.getElementById('submitBtn');
|
||||
const cardErrors = document.getElementById('card-errors');
|
||||
const depositStatus = document.getElementById('deposit-status');
|
||||
|
||||
btn.textContent = 'Processing…';
|
||||
btn.disabled = true;
|
||||
msg.className = 'form-msg';
|
||||
msg.style.display = 'none';
|
||||
if (cardErrors) { cardErrors.style.display = 'none'; cardErrors.textContent = ''; }
|
||||
|
||||
const data = {
|
||||
name: form.querySelector('[name="name"]').value,
|
||||
email: form.querySelector('[name="email"]').value,
|
||||
phone: form.querySelector('[name="phone"]').value,
|
||||
package: form.querySelector('[name="package"]').value,
|
||||
date: form.querySelector('[name="date"]').value,
|
||||
message: form.querySelector('[name="message"]').value,
|
||||
};
|
||||
function setDepStatus(text, type) {
|
||||
if (!depositStatus) return;
|
||||
if (!text) { depositStatus.style.display = 'none'; depositStatus.textContent = ''; return; }
|
||||
depositStatus.textContent = text;
|
||||
depositStatus.style.display = 'block';
|
||||
const map = {
|
||||
processing: { background: 'rgba(249,115,22,0.1)', color: 'rgba(249,115,22,0.95)', border: '1px solid rgba(249,115,22,0.25)' },
|
||||
success: { background: 'rgba(22,163,74,0.15)', color: '#86efac', border: '1px solid rgba(22,163,74,0.35)' },
|
||||
error: { background: 'rgba(239,68,68,0.1)', color: '#fca5a5', border: '1px solid rgba(239,68,68,0.25)' },
|
||||
};
|
||||
Object.assign(depositStatus.style, map[type] || {});
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/contact.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
let squareToken = null;
|
||||
if (squareCard) {
|
||||
setDepStatus('Verifying card…', 'processing');
|
||||
const result = await squareCard.tokenize();
|
||||
if (result.status !== 'OK') {
|
||||
const errMsg = result.errors ? result.errors.map(x => x.message).join(', ') : 'Card error — please check your details.';
|
||||
if (cardErrors) { cardErrors.textContent = errMsg; cardErrors.style.display = 'block'; }
|
||||
setDepStatus('', '');
|
||||
btn.textContent = 'Submit Booking Request';
|
||||
btn.disabled = false;
|
||||
return;
|
||||
}
|
||||
squareToken = result.token;
|
||||
setDepStatus('Card verified — authorizing $100 deposit hold…', 'processing');
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: form.querySelector('[name="name"]').value,
|
||||
email: form.querySelector('[name="email"]').value,
|
||||
phone: form.querySelector('[name="phone"]').value,
|
||||
package: form.querySelector('[name="package"]').value,
|
||||
date: form.querySelector('[name="date"]').value,
|
||||
message: form.querySelector('[name="message"]').value,
|
||||
square_token: squareToken,
|
||||
};
|
||||
|
||||
const res = await fetch('/contact.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
|
||||
const json = await res.json();
|
||||
|
||||
if (json.success) {
|
||||
if (json.deposit_held) {
|
||||
const holdSuffix = json.square_payment_id ? ' · Confirmation: …' + json.square_payment_id.slice(-10).toUpperCase() : '';
|
||||
setDepStatus('✓ $100 deposit hold authorized' + holdSuffix, 'success');
|
||||
const cardEl = document.getElementById('card-container');
|
||||
if (cardEl) cardEl.style.display = 'none';
|
||||
} else {
|
||||
setDepStatus('', '');
|
||||
}
|
||||
|
||||
msg.className = 'form-msg success';
|
||||
msg.innerHTML = json.message || 'Thanks! We received your request and will be in touch soon.';
|
||||
msg.innerHTML = 'Booking request received! Your reference is <strong>' + json.ref + '</strong>. We\'ll be in touch shortly.';
|
||||
msg.style.display = 'block';
|
||||
form.reset();
|
||||
if (squareCard) { squareCard.destroy(); squareCard = null; }
|
||||
selectedDate = null;
|
||||
if (dateInput) dateInput.min = new Date().toISOString().split('T')[0];
|
||||
btn.textContent = 'Request Sent!';
|
||||
btn.style.background = '#16a34a';
|
||||
// Refresh calendar to show newly booked date
|
||||
loadCalendar(calMonth, calYear);
|
||||
} else {
|
||||
throw new Error(json.error || 'Something went wrong.');
|
||||
}
|
||||
} catch (err) {
|
||||
setDepStatus('', '');
|
||||
msg.className = 'form-msg error';
|
||||
msg.textContent = err.message || 'Something went wrong. Please try again or call us directly.';
|
||||
msg.style.display = 'block';
|
||||
btn.textContent = 'Send Booking Request';
|
||||
btn.textContent = 'Submit Booking Request';
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user