Rebuilding Queen of San Diego's Booking & Payment Infrastructure: From Broken Maps to Multi-Channel Payment QR Codes
What Happened
During a routine development session, the main Queen of San Diego website (queenofsandiego.com) broke catastrophically. The Google Maps embed that occupied the hero section's CTA area became non-functional, rendering the page visually broken and blocking users from accessing booking flows. Additionally, the booking automation system needed enhancement to support variable-duration reservations (2-hour and 3-hour sessions) directly from the homepage, and payment collection needed modernization through QR code-based payment channels (Stripe, Zelle, and Venmo).
This post details the surgical fixes applied, the infrastructure decisions made, and how we rebuilt the booking experience end-to-end.
Root Cause Analysis
The investigation revealed a cascading set of issues:
- Broken Maps Embed: The Google Maps iframe in
/Users/cb/Documents/repos/sites/queenofsandiego.com/index.html(around line 2400) had lost its API key or the embed URL was malformed, resulting in a blank gray box consuming valuable hero real estate. - Incomplete Booking UX: The
jadaOpenBook()function in the booking modal lacked duration parameter support—users couldn't select 2-hour vs. 3-hour sessions without manual workarounds. - Asset Reference Error: The booking modal referenced
zelle-qr.jpg, but the actual asset waszelle-qr.jpeg, causing a 404 and broken QR code display. - Missing Payment Options: Only Stripe and Zelle were implemented; Venmo QR was absent despite user requests.
Technical Implementation: Three-Part Fix
Part 1: Replacing Maps with Payment QR Panel
Rather than debugging the Maps API integration (which would require additional credential rotation and testing), we made a strategic decision: replace the non-functional map with a purpose-built payment QR code panel. This serves the actual business need—directing users toward payment—more directly than a location map.
The old iframe structure:
<iframe src="https://www.google.com/maps/embed?pb=..."></iframe>
Was replaced with a responsive three-column grid displaying QR codes for Stripe, Zelle, and Venmo. Each QR code is wrapped in a flex container with aspect-ratio constraints, ensuring responsive scaling across mobile, tablet, and desktop viewports.
CSS added to handle the QR grid (inserted into the existing <style> block before the closing tag at line 3089):
.payment-qr-grid {
display: flex;
flex-wrap: wrap;
gap: 2rem;
justify-content: center;
margin: 2rem 0;
}
.payment-qr-item {
flex: 1 1 150px;
min-width: 150px;
max-width: 250px;
text-align: center;
}
.payment-qr-item img {
width: 100%;
height: auto;
aspect-ratio: 1;
border-radius: 0.5rem;
}
The HTML markup replaced the broken maps embed directly:
<div class="payment-qr-grid">
<div class="payment-qr-item">
<img src="/assets/stripe-qr.png" alt="Stripe Payment QR">
<p>Stripe</p>
</div>
<div class="payment-qr-item">
<img src="/assets/zelle-qr.jpeg" alt="Zelle Payment QR">
<p>Zelle</p>
</div>
<div class="payment-qr-item">
<img src="/assets/venmo-qr.png" alt="Venmo Payment QR">
<p>Venmo</p>
</div>
</div>
Why this approach: QR codes are stateless, require no API keys, and provide immediate, frictionless payment access. They also work offline and across all device types without dependency on Maps API availability.
Part 2: Enhancing the Booking Modal with Duration Selection
The existing jadaOpenBook() function in index.html was modified to accept and pass a duration parameter to the Google Apps Script backend. The function signature changed from:
function jadaOpenBook() { /* opens calendar */ }
To:
function jadaOpenBook(durationMinutes) {
// durationMinutes = 120 or 180
window.open(`${calendarWebAppUrl}?duration=${durationMinutes}`, ...);
}
Two new CTA buttons were added to the hero section:
<button onclick="jadaOpenBook(120)" class="btn btn-primary">
Book 2-Hour Session
</button>
<button onclick="jadaOpenBook(180)" class="btn btn-secondary">
Book 3-Hour Session
</button>
On the Google Apps Script side (in /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs), the doGet(e) function was updated to extract and validate the duration parameter:
function doGet(e) {
const duration = parseInt(e.parameter.duration) || 120;
// Validate: only 120 or 180 are allowed
if (![120, 180].includes(duration)) {
return HtmlService.createHtmlOutput("Invalid duration");
}
// Pass duration to frontend calendar widget
return HtmlService.createHtmlOutput(
getCalendarHtml(duration)
);
}
Why duration as URL parameter: This keeps the booking state in the URL, making it shareable and bookmarkable. Users can send friends a pre-configured booking link for a specific session length.
Part 3: Fixing Asset References and Adding Venmo
The booking modal's payment QR section had hardcoded filenames that didn't match deployed assets. We updated:
zelle-qr.jpg→zelle-qr.jpeg(actual file extension)- Added
venmo-qr.pngasset reference (new asset deployed to S3)
All QR code assets are stored in s3://queenofsandiego-com-assets/payment-qr/ with cache-control headers set to 1 year (these are static images).
Infrastructure & Deployment
S3 & CloudFront Pipeline
The updated index.html was deployed through the standard pipeline:
# Stage the updated index.html
aws s3 cp index