```html

Implementing Payment Logging for Patron Events: AWS Lambda, DynamoDB, and CloudFront Routing

What Was Done

This session implemented a payment logging feature for the Ship Captain Crew tool, enabling admins to record patron payments against specific events. The work involved three major components: backend Lambda handlers for payment processing, frontend UI modal for payment entry, and infrastructure routing fixes for event-specific URLs.

The core deliverable is a "Log Payment" modal that admins can trigger from the event dashboard, capturing payment details and persisting them to DynamoDB while maintaining audit trails through environment-based Gmail notifications.

Technical Details: Lambda Backend

The Lambda function (/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py) required two new handler functions inserted before the lambda_handler routing logic:

  • Email Helper Function: Wraps boto3 SES client to send structured JSON notifications to a configured Gmail address. Takes subject, body, and optional attachments. Uses environment variables SES_FROM_EMAIL and NOTIFICATION_EMAIL (stored as Lambda env vars, synced from secrets manager during deployment).
  • Payment Handler (handle_payment_log): Accepts POST requests to /api/events/{event_id}/payment. Validates incoming JSON schema (patron_name, amount, payment_method, timestamp). Performs DynamoDB UpdateItem on the events table, appending to a payments list attribute. Returns 200 with confirmation or 400 on validation failure. Triggers email notification via helper on successful log.
  • Admin Auth Middleware: Checks incoming request headers for admin token (via Authorization: Bearer {token}). Compares against ADMIN_PASS_HASH environment variable. Returns 401 if missing or invalid before routing to protected handlers.

The routing insertion point is in lambda_handler after path parsing (around line 1850 in the modified version). The payment route is registered as:

elif path.startswith('/api/events/') and path.endswith('/payment'):
    event_id = path.split('/')[3]
    if not is_admin(event):
        return error_response(401, 'Unauthorized')
    return handle_payment_log(event, event_id)

This pattern mirrors existing handlers like handle_waiver_get (line 1697) and handle_list_events, maintaining consistency with the codebase's routing convention.

Technical Details: Frontend UI

The dispatch HTML (/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html, deployed to S3 at queenofsandiego-shipcaptaincrew-prod) was updated with a new modal component. Key changes:

  • Modal Structure: Added <div id="payment-modal" class="modal"> following the existing modal pattern (display via active class toggle, not inline styles). Includes form fields for patron name, amount, payment method dropdown (Cash, Card, Check, Venmo), and timestamp (pre-populated from event date).
  • Event Card Enhancement: Modified the event card render function to include a "Log Payment" button that sets payment-modal.classList.add('active') and pre-fills hidden event_id field.
  • API Integration: New form submission handler uses existing apiFetch helper (line ~340) to POST to /api/events/{event_id}/payment. On success, clears form, closes modal, refreshes event list. On error, displays banner message via existing setBannerMessage() function.
  • Auth Context: Reuses existing admin token from session storage (set during login via handle_login handler). Token is included in all payment API requests via Authorization header.

The modal activation occurs in the existing renderEventCard() function around line 1200. Form submission validation checks for empty fields before API call, matching patterns used in other forms in the dispatch.

Infrastructure: CloudFront & S3 Routing

A critical bug was diagnosed during reconnaissance: event waivers at /g/{event_id}/waiver were being routed to S3 instead of Lambda, causing the SPA to misparse the path and return "Could not load event" errors. The fix requires adding a CloudFront behavior:

  • Distribution: shipcaptaincrew (CloudFront Distribution ID visible in AWS console)
  • New Behavior:
    • Path Pattern: /g/*/waiver
    • Origin: Lambda Function URL (not S3)
    • Viewer Protocol Policy: Redirect HTTP to HTTPS
    • Cache Policy: Managed-CachingDisabled (since Lambda returns dynamic HTML)
  • Placement: Insert this behavior BEFORE the existing catch-all S3 origin behavior (behaviors are evaluated top-to-bottom) to ensure specificity.
  • Invalidation: After deploying the behavior, invalidate the CloudFront cache with path pattern /g/* to purge any cached errors.

This fix prevents the SPA from attempting to parse event slugs as API requests, instead routing directly to Lambda's handle_waiver_get handler.

Key Decisions

  • DynamoDB List Append vs. Separate Table: Payments are appended to the event record itself (not a separate payments table) for atomic consistency and simpler access patterns. Event records are small; payment logs won't cause scalability issues.
  • Email Notification Pattern: Uses SES (not SNS or Slack) because Gmail credentials are already configured in Lambda env vars for other features. No new service dependencies.
  • Admin Auth Reuse: Payment endpoints check the same ADMIN_PASS_HASH used by the existing /api/admin/login handler, keeping credential management centralized.
  • CloudFront Behavior Specificity: The /g/*/waiver pattern must appear BEFORE the S3 origin catch-all to ensure it matches first. This is a common gotcha in CloudFront; ordering matters.
  • Modal Pattern Consistency: Used active class toggle (not display: none) to match existing modals, maintaining CSS consistency and respecting any animations or transitions already defined.

Deployment & Verification

The deployment pipeline (captured in session commands) follows this sequence:

  1. Snapshot current prod Lambda zip and HTML from S3
  2. Merge local edits with prod env vars (preserving Gmail token fields)
  3. Build Lambda deployment zip, push to function
  4. Wait for Lambda config update to settle (~2–3 seconds)
  5. Deploy updated HTML to S3, invalidate CloudFront cache
  6. Smoke-test admin login, then payment endpoint with real admin token