Rebuilding Queen of San Diego's Booking Infrastructure: From Broken Maps to Multi-Payment Gateway Integration
During a recent development session, we discovered that the Queen of San Diego homepage had degraded significantly—the Google Maps embed was broken, the booking flow was incomplete, and payment QR codes weren't integrated. This post details the multi-layered fixes we implemented across frontend markup, Google Apps Script, CloudFront distributions, and S3 infrastructure.
What Was Done
We executed three critical fixes to restore functionality:
- Replaced non-functional Google Maps iframe with a payment QR code panel (Stripe + Zelle + Venmo)
- Added direct 2-hour and 3-hour booking buttons to the hero CTA section
- Fixed file path references and enhanced the booking modal with duration parameters and date picker integration
Technical Details: Frontend Architecture
Marking Up Payment Options
The original design relied on an embedded Google Maps iframe that was returning 403 errors. We replaced it with a structured payment methods panel in /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html:
<div class="payment-qr-panel">
<h3>Payment Methods</h3>
<div class="qr-grid">
<div class="qr-item">
<img src="stripe-qr.jpg" alt="Stripe Payment">
<p>Credit/Debit</p>
</div>
<div class="qr-item">
<img src="zelle-qr.jpeg" alt="Zelle Payment">
<p>Zelle</p>
</div>
<div class="qr-item">
<img src="venmo-qr.jpg" alt="Venmo Payment">
<p>Venmo</p>
</div>
</div>
</div>
Why this approach: QR codes eliminate friction in the mobile booking flow. Users can scan and pay immediately without typing URLs or account numbers. The three-gateway approach accommodates different user preferences—credit card users prefer Stripe, bank transfer users use Zelle, and younger demographics favor Venmo.
Hero CTA Button Logic
We added duration-aware booking buttons to the hero section. The buttons now call the jadaOpenBook() function with explicit session duration parameters:
<button onclick="jadaOpenBook(120)">Book 2 Hours</button>
<button onclick="jadaOpenBook(180)">Book 3 Hours</button>
Duration as a parameter: Rather than hardcoding session length in the modal, we pass it as milliseconds. This allows the same modal function to handle multiple booking types without conditional branching, improving maintainability.
File Path Corrections
A subtle but critical bug existed: zelle-qr.jpg was referenced in the HTML, but the actual file was zelle-qr.jpeg. This caused 404s on the image asset. We corrected the extension throughout the markup and verified all three QR image files existed in the S3 bucket before deployment.
Infrastructure: S3 and CloudFront Distribution
S3 Bucket Structure
The website files are hosted in a CloudFront-backed S3 bucket. During this update, we modified:
index.html— primary homepage with all hero, modal, and payment markup- QR image assets (stripe-qr.jpg, zelle-qr.jpeg, venmo-qr.jpg) — stored in the same S3 bucket root
Deployment command for updated index.html:
aws s3 cp index.html s3://queenofsandiego-website/ --content-type "text/html"
Cache Invalidation
After pushing changes, we invalidated CloudFront's edge cache to ensure users received the updated HTML immediately. The distribution ID for queenofsandiego.com was queried via:
aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[0]=='queenofsandiego.com']"
Then invalidated with:
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/index.html"
Why full path invalidation: We only invalidated /index.html rather than /* to avoid unnecessary cache purges of static assets like CSS and JS bundles. This keeps origin load minimal while ensuring HTML is fresh.
Google Apps Script Integration
The booking modal hooks into Google Apps Script (GAS) for calendar synchronization and date availability. We verified the deployment URL and confirmed the web app endpoint existed in /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs.
The jadaOpenBook() JavaScript function now passes duration to GAS:
function jadaOpenBook(durationMinutes) {
const modal = document.getElementById("booking-modal");
modal.dataset.duration = durationMinutes;
modal.style.display = "block";
// Initialize date picker with GAS calendar endpoint
fetchAvailableDates(durationMinutes);
}
This allows GAS to filter available time slots based on session duration, preventing double-booking of shorter slots.
Key Decisions
- QR codes over links: Reduces friction on mobile; users scan and pay without manual URL entry
- Three payment gateways: No single gateway serves all user preferences. Stripe for cards, Zelle for bank transfers, Venmo for social payment
- Duration as function parameter: Keeps the booking modal generic and reusable across different session lengths
- Selective cache invalidation: Invalidate only changed assets to reduce origin load and CDN latency for cache misses
- GAS over serverless: Google Calendar native integration via Google Apps Script was simpler than building a Lambda-based alternative
What's Next
Pending improvements include:
- A/B testing payment gateway placement to determine optimal QR code ordering
- Adding analytics tracking to the booking flow funnel (currently in design phase)
- Implementing automated Zelle verification against the JADA calendar for instant confirmation
- Building a P&L calculator dashboard (already deployed to
pnl.queenofsandiego.comwith basic auth)
The fixes restore the critical path from homepage discovery to completed booking, with multiple payment options reducing checkout friction. All changes are live and cached globally via CloudFront.