```html

Implementing Patron Payment Logging for Queen of San Diego's Crew Management Tool

What Was Done

During this development session, we architected and partially deployed a payment logging system for the Ship Captain Crew management tool, enabling crew administrators to record patron payments directly through the dispatch dashboard. This required coordinating changes across three layers: AWS Lambda business logic, CloudFront routing configuration, and the client-side Single Page Application (SPA).

The work involved integrating Gmail SMTP credentials into Lambda's environment to support email notifications, designing a new payment modal UI component, implementing payment handler functions in Lambda, and establishing proper CloudFront behavior rules to distinguish between API routes and SPA catchall routes.

Technical Details

Lambda Function Architecture

The Lambda function lives at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py and serves as the primary backend for the SCC system. The function already had established patterns for:

  • Event routing via a lambda_handler that parses request paths
  • Authorization checks using admin token validation
  • DynamoDB queries against the SCC table schema
  • HTML response rendering for waiver and event pages

We inserted new payment handler functions before the lambda_handler definition to maintain clean separation of concerns. The new handlers follow the existing pattern: receive an event object, validate authorization, perform DynamoDB operations, and return appropriate HTTP responses.

Key insertion points in the Lambda source:

  • handle_payment_log() — Accepts patron ID, event ID, amount, and optional notes; validates admin credentials; writes to DynamoDB with timestamp
  • send_payment_notification() — Uses SES (Simple Email Service) to dispatch confirmation emails to administrators
  • Gmail SMTP helper functions — Integrate with Lambda's environment variables to support SMTP-based notifications as a fallback

The routing table in lambda_handler now includes new paths under the /api/ namespace:

/api/events/{event_id}/payment — POST to log a payment
/api/payment/list — GET to retrieve payment history for admin dashboard
/api/payment/{patron_id}/history — GET to fetch patron-specific payment records

Environment Variable Management

We merged existing Lambda environment variables with new Gmail SMTP credentials:

  • SCC_ADMIN_PASS_HASH — Already present; unchanged
  • GMAIL_SMTP_HOST, GMAIL_SMTP_PORT, GMAIL_FROM_ADDRESS — New; configured for SMTP relay
  • ADMIN_NOTIFICATION_EMAIL — New; destination for payment confirmation emails

Deployment workflow: merged local env payload with production snapshot using:

aws lambda update-function-configuration \
  --function-name shipcaptaincrew \
  --environment Variables={merged_env_dict}

Waited for Lambda config update to settle before deploying code changes.

Dispatch HTML (SPA) Integration

The dispatch HTML lives at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html and is served from the S3 bucket queenofsandiego.com (CloudFront distribution ID available in DNS records).

We added a new modal component following the existing pattern observed in the codebase:

  • showPaymentModal(eventId, patronId) — Renders a form with amount, optional notes, and submit button
  • Modal uses CSS class active for visibility (matching existing modal display patterns)
  • Form submission calls apiFetch('/api/events/{eventId}/payment', {method: 'POST', body: {...}})
  • Success callback displays confirmation toast and refreshes event card
  • Error callback displays error message and logs to browser console

The modal integrates with the existing event card render function, adding a "Log Payment" button that's only visible to authenticated admin users (checked against stored auth token from prior login).

CloudFront Routing Configuration

A critical diagnostic during reconnaissance revealed that CloudFront was incorrectly routing waiver requests to S3 instead of Lambda:

  • Issue: Request to /g/2026-05-23/waiver → S3 fallthrough → SPA tries to parse 2026-05-23/waiver as event_id → /api/g/2026-05-23/waiver returns HTML instead of JSON → r.json() throws → "Could not load event" error
  • Root cause: Missing CloudFront behavior rule for /g/*/waiver pattern
  • Fix: Add CloudFront behavior with pattern /g/*/waiver pointing to Lambda Function URL with precedence higher than the SPA catchall

Similarly, new payment API routes needed explicit CloudFront behaviors:

Pattern: /api/* → Lambda Function URL (highest precedence)
Pattern: /g/* → Lambda Function URL (precedence 2)
Pattern: /* → S3 origin + SPA fallback (lowest precedence, catchall)

Infrastructure Changes

  • S3 Bucket: queenofsandiego.com — Stores dispatch HTML and static assets; synced via CloudFront
  • Lambda Function: shipcaptaincrew — Deployed with updated code and environment variables
  • DynamoDB Table: SCC events table — No schema changes; payment records stored with timestamp and admin auth info
  • SES: Configured for transactional emails; sender address validated in AWS SES console
  • CloudFront Distribution: Updated behavior rules to prevent S3 fallthrough for API and waiver routes

Key Decisions

Why Gmail SMTP instead of Lambda-native SES only? Fallback reliability. If SES rate limits are hit or service degrades, SMTP relay provides redundancy. Also easier for ops to test locally during development.

Why POST /api/events/{event_id}/payment instead of PUT or PATCH? POST is idiomatic for state-changing operations that create new records (payment logs). Each log entry is immutable once created.

Why insert payment handlers before lambda_handler instead of alongside existing handlers? Keeps function definitions in a logical "library" section before the routing dispatcher. Easier to read and maintain.

Why add modal to SPA instead of separate admin page? Payment logging is a frequent operation on the same dashboard where crew view events. Keeping the modal in the dispatch SPA reduces context switching and keeps all admin tools in one view.

Testing & Validation

We performed smoke tests at each stage:

  • Admin login with existing credentials to verify auth flow still works
  • Probed new payment endpoints with real admin token to verify routing and auth