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 was zelle-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.jpgzelle-qr.jpeg (actual file extension)
  • Added venmo-qr.png asset 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