```html

Building a Payment Logging System for Event Management: Lambda, CloudFront, and DynamoDB Integration

This session focused on implementing a patron payment logging feature for the Ship Captain Crew event management tool hosted at queenofsandiego.com. The work involved coordinating changes across three layers: AWS Lambda backend, CloudFront routing, and the dispatch HTML frontend—all while maintaining backward compatibility with existing event and waiver handling.

The Problem Statement

The Ship Captain Crew tool lacked a mechanism for administrators to log payments directly from the event management interface. Previously, payment tracking existed only implicitly through event creation and crew assignment. The task required:

  • A new payment logging endpoint in the Lambda function
  • Modal UI in the dispatch SPA to accept and submit payment data
  • Integration with existing authentication and admin authorization patterns
  • Preservation of Gmail credential environment variables for future notification workflows
  • CloudFront behavior corrections for waiver routing

Infrastructure Reconnaissance

The first critical step was synchronizing the local development environment with production. The S3-hosted dispatch HTML (/tools/shipcaptaincrew/index.html) was 2463 lines, while the local version was only 976 lines—a significant drift that could have caused unexpected behavior. We pulled the current S3 version and compared it line-by-line before making any modifications.

Key infrastructure components identified:

  • Lambda Function: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py
  • S3 Dispatch Bucket: shipcaptaincrew S3 bucket (root-level dispatch HTML)
  • DynamoDB Table: Schema reviewed for event_id (partition key), timestamp (sort key), and extensible fields for payment metadata
  • CloudFront Distribution: Routes /api/* → Lambda Function URL; /g/* → S3 (with discovered routing bug for /g/*/waiver)

We also verified the Lambda environment variables contained Gmail OAuth credentials (SCC_GMAIL_* tokens), which needed preservation during deployment.

Lambda Backend: Payment Handler Implementation

The Lambda function required three additions before the main lambda_handler:

  • Gmail helper function: Encapsulates logic for constructing and sending SES emails with Gmail-authenticated credentials
  • Payment validation helper: Ensures required fields (event_id, amount, patron_name, payment_method) are present and valid before database write
  • Payment handler: New route handler responding to POST /api/events/{event_id}/payment

The payment handler follows the existing pattern in lambda_function.py (around line 1697 where handle_waiver_get is defined). It:

  • Extracts event_id from the URL path using the same regex pattern as other routes
  • Verifies admin authorization via existing verify_admin_token() helper
  • Parses JSON request body containing amount, patron_name, payment_method, and optional notes
  • Writes a payment record to DynamoDB with a new payment_cleared timestamp
  • Returns a 200 JSON response with the written record for client-side confirmation

The decision to use DynamoDB's existing event table (rather than a separate payments table) was deliberate: it keeps payment records colocated with event metadata, simplifies queries, and avoids cross-table consistency issues during later aggregation workflows. The payment record is stored as a nested object in a payments array attribute, preserving the denormalized structure already used for crew assignments.

Frontend: Modal UI and API Integration

The dispatch HTML required a new modal dialog and associated JavaScript. Key additions:

  • Modal Structure: Follows the existing pattern in index.html (around line 1200) where the "Add Event" and "Edit Event" modals are defined. Uses CSS class active to control visibility, avoiding inline style manipulation.
  • Form Fields: Amount (decimal input), Patron Name (text), Payment Method (select: cash/check/card/other), Notes (textarea)
  • Client-side Validation: Reuses the apiFetch() helper (defined earlier in the script) to POST to /api/events/{event_id}/payment
  • Error Handling: Catches network and API errors, displays toast messages (following existing UI patterns), and logs failures to browser console

The Log Payment button is anchored to each event card (in the card render function around line 980), appearing only if the user has admin credentials (checked via existing isAdmin() helper).

CloudFront and Routing Corrections

During testing, we discovered a waiver route bug: CloudFront behavior was routing /g/*/waiver to S3, which serves the dispatch SPA. The SPA then misinterpreted the path, attempting to fetch /api/g/2026-05-23/waiver and failing because it received HTML instead of JSON.

Fix implemented: Added a new CloudFront behavior:

  • Path Pattern: /g/*/waiver
  • Origin: Lambda Function URL (not S3)
  • Allowed Methods: GET, HEAD
  • Cache Policy: CloudFront-managed "Managed-CachingDisabled" (since waiver HTML is dynamic)

This ensures the Lambda handle_waiver_get handler (line 1697) receives the request directly and returns valid HTML.

Deployment and Testing

Deployment followed a staged approach:

  1. Lambda Code: Packaged the updated lambda_function.py, ensured all imports were present, and deployed via AWS Lambda console or SDK
  2. Lambda Environment: Merged new config with existing Gmail credentials and admin password hash; confirmed variables persisted post-update
  3. HTML to S3: Uploaded updated dispatch HTML to the shipcaptaincrew bucket, then invalidated /_staging/* on the CloudFront distribution to force cache refresh
  4. CloudFront Behaviors: Added /g/*/waiver behavior and cleared any relevant caches
  5. Smoke Tests: Verified admin login, confirmed /api/events returned valid JSON, tested payment POST with a real admin token, and checked that /g/2026-05-23/waiver now returns HTML without error

Key Design Decisions

  • DynamoDB Denormalization: Payment records stored inline with event data, not in a separate table, to simplify admin queries and keep the single-event fetch operation fast.
  • Modal-Based UI: Reused existing modal infrastructure rather than creating a new drawer or popover, reducing CSS and JavaScript duplication.
  • Admin-Only Access