Rebuilding the Queen of San Diego Booking System: From Broken Maps to Integrated Payment QR Codes
During a recent development sprint, we discovered that the main booking page for queenofsandiego.com had degraded significantly—a broken Google Maps embed and missing payment integration were creating friction in the customer journey. This post documents the technical approach we took to rebuild the homepage experience, wire in three payment methods simultaneously, and streamline the booking flow from a single hero CTA.
The Problem: Multiple Points of Failure
The site's index.html at /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html had three interconnected issues:
- Broken Google Maps iframe: The embedded map was no longer rendering, leaving a blank space where location context should be
- Missing payment QR codes: Customers had no way to initiate payment directly from the homepage without navigating to a separate payment page
- Unclear booking CTA: The hero section's call-to-action didn't provide direct paths for the most common booking durations (2 hours vs. 3 hours)
Additionally, there was a file naming inconsistency: the Zelle QR code was referenced as zelle-qr.jpg but actually existed as zelle-qr.jpeg, causing a 404 on image load.
Technical Details: Three Surgical Changes
1. Replacing Maps with a Payment QR Panel
Rather than debugging the Maps embed (which required API key rotation and CORS configuration), we made a product decision: replace the location context with immediate payment options. This optimizes for conversion over exploration.
The original embed was located in the hero section of index.html. We replaced it with a responsive grid containing three payment QR code images:
- Stripe: Primary digital payment method (secure, PCI-compliant)
- Zelle: Direct bank transfer for domestic US customers
- Venmo: Peer-to-peer option for informal payments
Each QR code is wrapped in a <figure> element with descriptive alt text for accessibility. The grid uses CSS flexbox for mobile responsiveness:
.payment-qr-grid {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 2rem;
margin: 2rem 0;
}
2. Wiring Duration-Specific Booking Buttons
The original "Reserve" button in the navigation bar triggered a generic booking modal. We enhanced the jadaOpenBook() function (defined in the inline JavaScript within index.html) to accept a duration parameter:
jadaOpenBook(durationHours) {
// Opens the booking modal with pre-selected duration
// Duration is passed to the GAS backend via CalendarSync
}
The hero section now includes two prominent CTA buttons:
<button onclick="jadaOpenBook(2)">Book 2 Hours</button><button onclick="jadaOpenBook(3)">Book 3 Hours</button>
This eliminates an extra step—customers no longer need to select duration in a modal after clicking Reserve. They commit to a duration upfront.
3. Fixing File References and Modal Integration
We corrected the Zelle QR code image path throughout the DOM, ensuring all references use the correct .jpeg extension. We also updated the booking modal's event listeners to ensure the date picker properly communicates with the Google Apps Script backend at /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs.
Infrastructure & Backend Integration
Google Apps Script Changes
The BookingAutomation.gs file needed a new endpoint to expose available booking dates. We added a booked_dates endpoint to the existing doGet handler, which queries the JADA calendar and returns already-booked slots as JSON. This allows the frontend date picker to disable conflicting time slots in real-time.
The GAS deployment was updated via clasp (Google Apps Script CLI):
clasp push
clasp deploy --description "Add booked_dates endpoint"
This created a new deployment version that the frontend can call via:
fetch('https://script.google.com/macros/d/{SCRIPT_ID}/useless/exec?action=booked_dates')
.then(res => res.json())
.then(dates => updateCalendarPicker(dates))
CloudFront & S3 Deployment
The updated index.html was deployed to the main S3 bucket serving queenofsandiego.com. We then invalidated the CloudFront distribution cache to ensure all edge locations served the new version immediately:
aws s3 cp index.html s3://queenofsandiego.com/
aws cloudfront create-invalidation \
--distribution-id {DIST_ID} \
--paths "/index.html" "/*"
The CloudFront distribution ID is stored securely in our ops infrastructure and referenced via environment variables during CI/CD.
Key Architectural Decisions
- Payment QR codes over embedded payments: By using QR codes instead of embedded payment forms, we avoid PCI scope expansion and reduce third-party JavaScript dependencies. Customers can securely scan and pay on their preferred platform.
- Duration parameter in booking function: Rather than adding a new booking endpoint, we extended the existing function signature. This reduces backend complexity and leverages the existing calendar-sync logic.
- Real-time date picker validation: Fetching booked dates from the GAS backend ensures the UI stays in sync with the actual calendar, eliminating double-bookings without polling or WebSocket overhead.
- Edge caching strategy: We invalidated the entire distribution rather than waiting for cache expiry (24 hours). This ensures customers see the new payment options immediately, which is critical for conversion optimization.
Testing & Validation
Post-deployment validation included:
- HTTP 200 response checks for all QR code image URLs
- JavaScript console verification that
jadaOpenBook(2)andjadaOpenBook(3)trigger the modal correctly - Booking date picker functionality against the live GAS backend
- Mobile responsiveness testing for the payment QR grid
What's Next
Future enhancements under consideration:
- Analytics tracking on QR code scans to measure payment method preference
- A/B testing different hero CTA copy to optimize for 2-hour vs. 3-hour bookings
- Integrating Apple Pay / Google Pay buttons alongside QR codes for faster mobile checkout
- Extending the
booked_datesendpoint to return pricing tiers by duration, enabling dynamic quote generation
The changes stabilized the homepage experience and directly connected customer intent (booking duration) with payment options, reducing friction in the conversion funnel.