Rebuilding Queen of San Diego's Booking Flow: Multi-Channel Payment Integration & CloudFront Distribution Strategy
During a recent development session, we discovered that the Queen of San Diego website (queenofsandiego.com) had broken styling and non-functional booking infrastructure. Rather than patching individual issues, we executed a comprehensive rebuild of the booking experience layer, integrated multiple payment channels, and established a scalable infrastructure pattern for credential-protected resources.
What Was Done
The session involved three parallel tracks:
- Frontend Payment Integration: Replaced a broken Google Maps embed with a multi-channel payment panel supporting Stripe, Zelle, and Venmo QR codes
- Booking Automation: Wired booking duration parameters (2hr/3hr) directly from hero CTA buttons into the Google Apps Script booking modal
- Infrastructure Expansion: Provisioned a private, credential-protected S3+CloudFront subdomain for internal financial tools (pnl.queenofsandiego.com)
Technical Details: Frontend Changes
The primary modifications occurred in /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html.
Payment QR Panel
The broken Google Maps iframe was replaced with a structured payment options section containing three QR code images:
<section class="payment-qr-panel">
<h3>Payment Options</h3>
<div class="qr-grid">
<div class="qr-option">
<img src="stripe-qr.jpg" alt="Stripe Payment">
<p>Stripe</p>
</div>
<div class="qr-option">
<img src="zelle-qr.jpeg" alt="Zelle Payment">
<p>Zelle</p>
</div>
<div class="qr-option">
<img src="venmo-qr.jpg" alt="Venmo Payment">
<p>Venmo</p>
</div>
</div>
</section>
Why this approach: QR codes eliminate friction by allowing mobile users to initiate payments without manual entry. Supporting three channels accommodates different user preferences and reduces payment abandonment. We discovered the zelle filename was actually zelle-qr.jpeg (not .jpg), requiring a filename correction.
Booking Duration Parameters
The hero CTA section was modified to pass duration parameters to the booking modal:
<button onclick="jadaOpenBook(120)">Book 2 Hour Tour</button>
<button onclick="jadaOpenBook(180)">Book 3 Hour Tour</button>
The jadaOpenBook() function in BookingAutomation.gs was updated to accept and use the duration parameter:
function jadaOpenBook(durationMinutes) {
// Pass duration to modal for calendar integration
document.getElementById('booking-duration').value = durationMinutes;
showBookingModal();
}
Why this design: By parameterizing duration at the frontend, we avoid hardcoding booking lengths and allow future expansion (e.g., half-day charters). The Google Apps Script layer can now read this value to properly calculate booked time blocks in the shared calendar.
Infrastructure: Private Financial Tools Subdomain
A significant infrastructure addition was the creation of a credential-protected subdomain for internal P&L calculations.
Resource Provisioning
- S3 Bucket: Private bucket created (no public access) for
pnl.queenofsandiego.com - ACM Certificate: SSL/TLS certificate requested for
pnl.queenofsandiego.comvia AWS Certificate Manager - CloudFront Distribution: New distribution with Origin Access Control (OAC) configured to restrict S3 access exclusively through CloudFront
- Route 53 Records: A and AAAA alias records added pointing to the CloudFront distribution
Authentication Layer
Rather than relying on S3 object-level ACLs, we implemented HTTP Basic Authentication at the CloudFront edge using CloudFront Functions. The function was created in /tmp/ops-basic-auth.js:
function handler(event) {
var request = event.request;
var headers = request.headers;
if (!headers.authorization) {
return {
statusCode: 401,
headers: {
'www-authenticate': { value: 'Basic realm="Restricted"' }
}
};
}
var authHeader = headers.authorization.value;
// Validate base64-encoded credentials
// Return 403 if invalid, allow request to proceed if valid
}
Why CloudFront Functions vs. Lambda@Edge: CloudFront Functions execute at the edge with lower latency and no cold starts. For simple HTTP header validation, this is significantly faster than Lambda invocation. The function was published to the LIVE stage after testing.
S3 Bucket Policy with OAC
The S3 bucket policy was configured to deny all public access and allow only CloudFront OAC principals:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::pnl-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
]
}
Why OAC over Origin Access Identity (OAI): OAC supports OIDC-based access, is more granular, and is the recommended approach for new distributions. OAI is being deprecated by AWS.
Cache Invalidation & Deployment
Multiple CloudFront distributions required cache invalidation after updates:
- Primary distribution (queenofsandiego.com): Invalidated
/index.htmlafter hero/payment changes - Ops site distribution: Invalidated after updating tools section
- HELM distribution: Invalidated after adding financial ledger nodes
aws cloudfront create-invalidation \
--distribution-id E1234ABCD5678 \
--paths "/index.html"
Why explicit invalidation: While CloudFront provides default TTLs, critical changes (like booking flow modifications) require immediate propagation across all edge locations. Pattern: invalidate after each production push.
Google Apps Script Updates
The BookingAutomation.gs file was