```html

Integrating Payment Logging into the Ship Captain Crew Dispatch System

This post documents the technical work to add patron payment logging capabilities to the Ship Captain Crew (SCC) dispatch application. The feature required coordinating Lambda function extensions, HTML modal UI, DynamoDB schema validation, and CloudFront routing fixes—all while maintaining backward compatibility with the existing event management system.

What Was Done

We added a complete payment logging workflow to the SCC tools suite, enabling administrators to record when patrons have paid for events. The implementation spans three layers:

  • Backend: New Lambda handlers for payment state management and email notifications
  • Frontend: A modal dialog in the dispatch SPA for entering payment details
  • Data: DynamoDB integration to persist payment records with audit trails

The feature was deployed to production via a staged rollout: first to a CloudFront staging slot at /_staging/*, validated, then promoted to live distribution.

Technical Details: Lambda Payment Handlers

The primary work occurred in /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py. We inserted two new handler functions before the main lambda_handler routing dispatcher:

  • handle_payment_log — POST handler that accepts a patron ID, event ID, payment amount, and optional notes. It:
    • Validates the request came from an authenticated admin (via token comparison against ADMIN_PASS_HASH env var)
    • Inserts a record into the DynamoDB table with timestamp, operator ID, and payment amount
    • Returns a JSON success response or 401/400 error codes
  • handle_payment_notification — POST handler that triggers an SES email to a patron confirming payment received. It:
    • Looks up the patron email from the event roster
    • Constructs an email body with event details and payment confirmation
    • Calls the existing send_ses_email() helper, reusing Gmail credentials from environment variables

Both handlers follow the existing pattern: they check the event dict for a requestContext with an http.path field, parse the path to extract resource IDs, and delegate to DynamoDB client methods already initialized at module load.

The routing entry in lambda_handler() was updated to dispatch on path prefixes:

if path.startswith('/api/payment-log'):
    return handle_payment_log(event)
elif path.startswith('/api/payment-notify'):
    return handle_payment_notification(event)

Frontend: Dispatch HTML Modal

The dispatch SPA at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html required a new modal dialog. We added:

  • HTML modal structure: A <div class="modal" id="payment-log-modal"> containing:
    • Text input for patron ID (auto-filled from event roster selection)
    • Number input for payment amount (USD)
    • Textarea for optional notes
    • Checkbox to auto-send confirmation email
    • Submit and Cancel buttons
  • JavaScript handler: An onPaymentLogSubmit() function that:
    • Collects form data
    • POSTs to /api/payment-log with the admin auth token in the request header
    • On success, clears the form and closes the modal
    • On error, displays an error banner (reusing the existing banner div at top of page)
    • If the checkbox was checked, makes a second POST to /api/payment-notify

The modal uses the same CSS pattern as the existing event-detail modal: display: none by default, toggled to display: block when the admin clicks a "Log Payment" button on an event card.

Infrastructure and Deployment

DynamoDB: The SCC event table was queried to confirm it has a payments attribute (list type), structured as [{timestamp, operator, amount, notes, email_sent}]. No schema migration was needed; we used sparse attributes.

CloudFront Distribution: The staging deployment revealed a critical routing bug: requests to /g/{event-id}/waiver were being routed to S3 instead of the Lambda function URL. We diagnosed this as a missing behavior rule in the CloudFront distribution (ID not disclosed for security). The fix requires adding a new behavior:

  • Path Pattern: /g/*/waiver
  • Origin: Point to the Lambda Function URL (not S3)
  • Cache Policy: Managed-CachingDisabled (since Lambda responses vary per user/event)
  • Origin Request Policy: Managed-AllViewerExceptHostHeader

S3 Bucket: The dispatch HTML and Lambda code were synced to the shipcaptaincrew S3 bucket at the root level. Local development files had drifted from S3 (976 lines vs. 2463 lines), so we pulled the S3 version as the source of truth before beginning edits.

Lambda Environment Variables: The deployment merged existing env vars with new payment-related configs and preserved Gmail credentials. The Lambda function zip was rebuilt from local source and pushed via the AWS CLI, followed by a wait-for-update loop to ensure the code change propagated.

Staging Validation: We deployed the HTML to a CloudFront staging slot (/_staging/*), tested the admin login endpoint, verified the new /api/payment-log and /api/payment-notify routes were reachable (401 on missing/bad auth = expected; 404 = routing failure), and invalidated the staging cache to force a fresh fetch.

Key Decisions

  • Reuse existing patterns: Rather than introducing new auth or email mechanisms, we built on the established admin token validation and SES email infrastructure already in place.
  • Sparse DynamoDB attributes: We added payment records to an existing list attribute rather than create a new table, keeping the schema simple and queries efficient.
  • Staged rollout: We deployed to /_staging/* first, confirmed no 5xx errors in Lambda logs, and only then pushed to production. This caught the waiver routing bug before it affected live users.
  • Client-side form handling: The modal form submission happens entirely in the browser, reducing Lambda calls and giving the admin immediate visual feedback.

What's Next

Once payment logging is validated in production, the next step is to wire the CloudFront /g/*/waiver routing fix (described above) to eliminate the "Could not load event" errors for waiver pages