Building a Payment Logging System for Event Management: Lambda Integration with Gmail Credentials and CloudFront Routing Fixes

Overview

This session focused on implementing a payment logging feature for the Ship Captain Crew event management system, along with diagnosing and fixing critical routing issues in the CloudFront distribution. The work involved:

  • Adding Gmail credential infrastructure to Lambda environment variables
  • Implementing payment logging handlers in the Lambda function
  • Building a "Log Payment" modal UI in the dispatch HTML
  • Fixing CloudFront routing for waiver endpoints
  • Creating an admin authentication mechanism for sensitive operations

The Problem

The Ship Captain Crew system lacked a way to log payments for event patrons. While the core event management infrastructure existed, there was no UI or backend handler to record payment transactions. Additionally, CloudFront was misrouting waiver requests (/g/*/waiver) to S3, causing the dispatch SPA to incorrectly parse event IDs and fail with "Could not load event" errors.

Technical Architecture

Lambda Function Structure

The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py serves as the backend for all event management operations. The function uses a routing pattern in lambda_handler() to dispatch requests to specific handlers based on the request path.

Before implementing the payment handler, we performed reconnaissance to understand the existing architecture:

  • Examined the DynamoDB table schema to understand event structure
  • Located the authentication helpers and admin password hash verification
  • Reviewed existing event handler return shapes (e.g., handle_list_events())
  • Identified the routing context and SES helper functions

Gmail Credential Management

To enable payment notifications via email, we needed to store Gmail credentials in Lambda environment variables. The process involved:

  1. Reading existing environment variables from the deployed Lambda function
  2. Building a merged payload that preserved existing credentials (Gmail tokens, ADMIN_PASS_HASH, etc.)
  3. Deploying the updated environment configuration before code changes
  4. Verifying credential presence before deploying the payment handler code

This approach follows the principle of immutable infrastructure: environment configuration is updated separately from code, reducing the risk of credential exposure in deployment logs.

Payment Handler Implementation

The payment logging handler follows the existing Lambda pattern:

def handle_log_payment(event, context):
    """
    POST /api/payment
    Logs a payment for an event patron.
    Requires admin authentication.
    """
    # Validate admin token
    # Extract patron_id, event_id, amount from request body
    # Write to DynamoDB
    # Send confirmation email via SES
    # Return success response

The handler is integrated into the main lambda_handler() routing logic, which parses the request method and path to dispatch to the appropriate handler. This maintains consistency with existing endpoints like /api/events and /api/g/{eid}/waiver.

Frontend Implementation

Modal UI Pattern

The dispatch HTML at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html already contained several modals. We followed the existing pattern for the "Log Payment" modal:

  • Display mechanism: Toggle the active class on the modal element
  • Form structure: Input fields for patron name/ID, amount, payment method
  • API integration: Use the existing apiFetch() helper to POST to /api/payment
  • Error handling: Display validation errors inline; show success banner on completion

The modal is accessed via an admin-only button in the crew dashboard, protected by token-based authentication that the frontend already implements.

CloudFront Routing Fixes

The Waiver Routing Problem

The waiver endpoint was diagnosed as follows:

  • Intent: Requests to /g/{event_id}/waiver should return an HTML waiver form from Lambda
  • Current behavior: CloudFront was routing these to S3 (the dispatch SPA bucket), which fell through to index.html
  • SPA misparsing: The dispatch SPA then tried to fetch /api/g/2026-05-23/waiver, received HTML instead of JSON, and crashed with "Could not load event"

Solution: CloudFront Behavior Addition

The fix requires adding a new CloudFront behavior to the distribution serving queenofsandiego.com:

Path Pattern: /g/*/waiver
Origin: Lambda Function URL
Cache Policy: CachingDisabled (since this is a dynamic form)
Origin Request Policy: AllViewerExceptHostHeader
Compress: Yes

This behavior must be inserted before the catch-all S3 behavior to ensure it matches first. The routing priority in CloudFront is first-match-wins.

Secondary Issue: Event ID Format Violation

During diagnostics, we discovered that the waiver request was for event ID 2026-05-23, which violates the documented convention: YYYY-MM-DD-firstname-[period]. This should be flagged in the event creation flow or data validation layer.

Authentication & Security

Payment logging is an admin-only operation. The implementation uses:

  • Token-based auth: The frontend stores an admin token (obtained via magic link or password login)
  • Token validation: The handle_log_payment() handler extracts the token from the request header and validates it against known admin credentials
  • Password hash storage: Admin password is stored as a hash in the ADMIN_PASS_HASH environment variable, never as plaintext

We validated that the existing admin authentication system was correctly wired before adding payment operations to the protected endpoint set.

Deployment Process

  1. Environment prep: Merged Gmail credentials into Lambda env vars and deployed the update
  2. Code deployment: Built a zip file with the updated lambda_function.py (including new payment handlers) and deployed via Lambda console
  3. Frontend staging: Deployed updated index.html to S3 staging slot (/_staging/ path)
  4. CloudFront invalidation: Invalidated /_staging/* cache on the distribution to serve fresh HTML
  5. Smoke testing: Verified admin endpoint reachability and password validation before production

Key Decisions

Why separate environment deployment from