```html

Building a Payment Logging System for Event Crew: Lambda Handlers, DynamoDB Integration, and CloudFront Routing Fixes

Overview

During this session, we built out a complete payment logging feature for the Ship Captain Crew tool—a web application for managing charter events and crew assignments. The work involved three major areas: extending the Lambda function with Gmail credential retention and payment handlers, adding a "Log Payment" modal to the frontend dispatch HTML, and diagnosing (though not yet fixing) a CloudFront routing issue affecting waiver page delivery.

What Was Done

The core objective was to enable crew members to log patron payments directly within the event management interface. This required:

  • Adding Gmail OAuth token persistence to the Lambda environment variables
  • Building new payment handler functions in lambda_function.py
  • Creating a modal UI component in index.html for payment entry
  • Integrating payment logging into the DynamoDB events table schema
  • Deploying to a staging CloudFront distribution for testing before production rollout

Technical Details: Lambda Architecture

The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py serves as the backend for all Ship Captain Crew operations. We extended it by:

  • Preserving Gmail Credentials: The existing environment variables stored Gmail sender credentials. We verified these were present and merged them into the Lambda environment update payload to ensure payment notification emails could be sent after logging.
  • Adding Payment Handler Functions: Inserted new handlers before the main lambda_handler routing logic. These handlers follow the existing pattern: accept an event object with required fields, validate inputs, update the DynamoDB table, and return a structured response.
  • DynamoDB Schema Extension: The existing events table schema was inspected to understand the payment field structure. Rather than creating a separate payments table, we extended individual event items with a payments attribute—an array or map of payment records keyed by timestamp or transaction ID.

The Lambda routing at the handler entry point inspects the path from the API Gateway event and dispatches to the appropriate handler. Payment logging was added as a new route (e.g., /api/events/{event_id}/payment) that maps to the payment handler function.

Frontend Integration: Dispatch HTML Modal

The dispatch HTML at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html is a single-page application that renders event cards and manages UI state. We added a modal component for payment logging that:

  • Follows Existing Modal Patterns: Examined existing modal structures (e.g., event detail modals) and replicated their display logic. Modals are shown by adding an active class or toggling inline styles.
  • Integrates with API Fetch Helper: The HTML includes an apiFetch utility function that handles authentication and error handling. The payment modal form submission uses this helper to POST payment data to the Lambda endpoint.
  • Card Render Integration: Event cards now include a "Log Payment" button that opens the modal and sets the current event ID in the form's hidden field.
  • Form Fields: The payment form collects patron name, amount, payment method, and notes. These are sent as JSON to the Lambda function.

The frontend code iteratively evolved through multiple edits to index.html` as we refined the modal structure, button placement, and form validation. The final version integrates seamlessly with the existing event card UI and banner system.

Infrastructure: S3, CloudFront, and Deployment

S3 Bucket: The shipcaptaincrew dispatch HTML and static assets are hosted in an S3 bucket. We pulled the current production version from S3 and compared it to the local copy, discovering the local version was stale (2463 lines in S3 vs. 976 locally). We synced local with S3 first, then applied new changes.

CloudFront Distribution: The shipcaptaincrew tool is served through a CloudFront distribution that caches the dispatch HTML and routes API calls to the Lambda Function URL. For staging, we deployed the updated HTML to a staging slot and invalidated the /_staging/* cache pattern to force immediate refreshment.

Lambda Deployment: The updated lambda_function.py was packaged into a zip file with all dependencies and deployed to the Lambda function. We verified the code update settled and smoke-tested new routes with a 401 response (indicating the route was wired to Lambda) before testing with actual credentials.

Environment Variables: Merged environment variable payloads preserved existing Gmail OAuth tokens and admin credentials while adding new configuration for payment logging. All variables were verified in the Lambda console before and after the update.

Authentication and Admin Workflow

The system uses magic-link authentication for crew members. Admin password verification is handled by comparing a hash in the request against an ADMIN_PASS_HASH environment variable. We verified the admin account was properly configured by testing login with real credentials and confirming the payment logging endpoint returned 401 when unauthenticated (proving it was wired) and 200 with valid credentials.

Diagnosed Issue: CloudFront Waiver Routing

During diagnostics, we identified a routing bug affecting waiver pages. The Lambda function includes a working handle_waiver_get handler at /g/{eid}/waiver that returns HTML (line 1697 in lambda_function.py). However, CloudFront routes all /g/*/waiver requests to S3, which falls through to the dispatch SPA. The SPA mis-parses the waiver slug (e.g., 2026-05-23/waiver) as an event ID and attempts to fetch it via /api/g/2026-05-23/waiver, receiving HTML instead of JSON. The JSON parser then throws an error, caught and displayed as "Could not load event" (line 1385 in dispatch HTML).

Fix Required: Add a CloudFront behavior that routes /g/*/waiver → Lambda Function URL instead of S3. This is a single-line configuration change in the CloudFront distribution.

Key Decisions

  • Single Payment Array vs. Separate Table: We chose to extend the existing events table with a payments array rather than creating a separate DynamoDB table. This reduces operational overhead, keeps related data together, and simplifies queries for a given event's payment history.
  • Staging Deployment Before Production: All changes were deployed to a staging slot (with /_staging path prefix) and invalidated in CloudFront. This allowed for testing without affecting live crew members and provided a clear promotion path to production.
  • Preserving Existing Environment Variables: Rather than replacing the Lambda environment wholesale, we merged new variables into the existing set. This prevented accidental loss of Gmail credentials and other secrets.
  • Frontend-First Modal Pattern: We followed the existing modal display pattern (active class + style toggles) rather than introducing new UI frameworks. This keeps the codebase homogeneous and reduces bundle size.

What's Next

The payment logging feature is ready for crew