```html

Implementing Event Payment Logging for ShipCaptainCrew: Lambda, DynamoDB, and CloudFront Routing

What Was Done

This session implemented a payment logging feature for the ShipCaptainCrew event management system, allowing crew administrators to record patron payments directly from the event dispatch interface. The work involved three major components: backend Lambda function enhancements, frontend HTML/JavaScript modal UI, and CloudFront routing fixes for event waiver pages.

Architecture Overview

ShipCaptainCrew is a serverless architecture deployed on AWS:

  • Frontend: Static dispatch SPA served from S3 bucket shipcaptaincrew through CloudFront distribution
  • Backend: AWS Lambda function handling all API requests via CloudFront function URL integration
  • Data Layer: DynamoDB table storing events and crew rosters
  • Email: Amazon SES for administrative notifications

Technical Details: Lambda Payment Handler

The core payment logging feature was implemented as a new Lambda handler in /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py.

Handler Signature:

def handle_log_payment(event, context):
    """
    POST /api/g/{eid}/payment
    Records a patron payment against an event.
    Requires admin authentication via token validation.
    """

The handler:

  • Validates the admin authentication token against environment variables (token stored in GMAIL_ADMIN_TOKEN env var)
  • Parses the event ID and payment details from the request body
  • Updates the DynamoDB events table with payment metadata (amount, timestamp, admin who logged it)
  • Returns a JSON response indicating success or failure
  • Follows the existing routing pattern already in place for other event endpoints

DynamoDB Schema Integration: The table schema includes a payments field on event items (list of payment objects). Each payment object contains:

  • amount (Number)
  • logged_at (ISO 8601 timestamp)
  • logged_by (admin user identifier)
  • notes (optional payment notes)

This was discovered by scanning existing event items in the DynamoDB table to understand the data model before implementing the handler.

Frontend Implementation: Payment Modal

The dispatch HTML at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html received a new "Log Payment" modal component following the existing modal pattern in the codebase.

Modal Structure: The modal uses the same active-class display pattern as existing modals (waiver modal, event details modal). CSS class active controls visibility.

Key JavaScript Functions Added:

  • openPaymentModal(eventId) — Opens the modal and sets the event context
  • submitPayment() — Validates form input, calls apiFetch() to POST to /api/g/{eid}/payment
  • closePaymentModal() — Closes the modal and resets form state

The modal is triggered from the event card render function, adding a "Log Payment" button visible only to authenticated admins. The button checks the same authentication context used for other admin actions (waiver access, crew roster editing).

API Integration: The existing apiFetch() helper function in the dispatch HTML was leveraged, which already handles:

  • Authorization token injection into request headers
  • JSON serialization of request/response bodies
  • Error handling with user-facing error messages

Infrastructure: CloudFront Routing Fix

During testing, a critical issue was discovered: waiver page requests (e.g., /g/2026-05-23/waiver) were being routed to the S3 origin instead of the Lambda function URL, causing the SPA to attempt to parse the request as an event ID and fail with "Could not load event."

Root Cause: CloudFront distribution for shipcaptaincrew lacked a behavior rule for /g/*/waiver paths pointing to the Lambda function URL.

Fix Applied: Added new CloudFront behavior:

  • Path Pattern: /g/*/waiver
  • Origin: Lambda function URL (not S3)
  • Cache Policy: Managed-CachingDisabled (waivers are dynamic HTML, should not be cached)
  • Origin Request Policy: AllViewerAndCloudFrontHeaders (preserves auth tokens)

This ensures all requests matching the waiver route go directly to Lambda's handle_waiver_get() function (defined at lambda_function.py:1697) rather than falling through to the SPA.

Deployment Process

Lambda Deployment:

# Build deployment zip including updated lambda_function.py
zip -r /tmp/shipcaptaincrew-lambda.zip lambda_function.py

# Deploy via AWS CLI (credentials via environment)
aws lambda update-function-code \
  --function-name shipcaptaincrew \
  --zip-file fileb:///tmp/shipcaptaincrew-lambda.zip

# Wait for code update to settle (30-60 seconds)
aws lambda wait function-updated \
  --function-name shipcaptaincrew

Environment Variables: Merged new configuration (Gmail credentials for payment notification emails) with existing env vars before deployment using a JSON payload:

aws lambda update-function-configuration \
  --function-name shipcaptaincrew \
  --environment Variables={KEY1=value1,KEY2=value2,...}

HTML Deployment:

# Sync updated dispatch HTML to S3
aws s3 cp index.html \
  s3://shipcaptaincrew/index.html \
  --content-type text/html

# Invalidate CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id ABCD1234EFGH5678 \
  --paths "/*"

Testing Strategy

Before deploying to production, changes were deployed to a staging slot:

  • Updated HTML pushed to S3 staging path (/_staging/index.html)
  • Lambda staging alias updated with new code
  • Admin login tested with real credentials to confirm payment handler routing works
  • Payment modal UI verified in browser dev tools
  • API calls to /api/g/{eid}/payment traced in Lambda CloudWatch logs to confirm proper execution

Key Design Decisions

Why