Building a Payment Logging System for Event Crew: Lambda Handlers, DynamoDB Integration, and CloudFront Routing Fixes
What Was Done
This session implemented a payment logging feature for the Ship Captain Crew event management platform, enabling admins to record patron payments directly in the web UI. The work involved three major components: extending the Lambda function with Gmail credential helpers and payment handlers, adding a modal UI to the dispatch HTML, and fixing a CloudFront routing bug that was causing waiver pages to fail.
Technical Architecture
Lambda Function Enhancements
The core work happened in /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py. The existing Lambda was already serving as the API backend, handling event CRUD operations against a DynamoDB table and routing HTTP requests through a dispatch pattern.
Two new helper functions were inserted before the lambda_handler:
- Gmail credential validators: Helper functions to safely retrieve and validate SES/Gmail SMTP credentials from Lambda environment variables (
GMAIL_USER,GMAIL_TOKEN), with fallback patterns to handle missing credentials gracefully. - Payment handlers: A new
handle_log_paymentfunction that accepts POST requests to a new/api/payment/logendpoint. This handler validates the request payload (event_id, amount, patron_name, notes), writes a payment record to the DynamoDB events table with apaymentsattribute (a list of dicts), and returns a 200 success response.
The payment handler follows the existing Lambda routing pattern: it's wired into the lambda_handler's request routing logic (checking the HTTP method and path), performs basic auth checks (verifying the admin token from the request context), and returns structured JSON responses.
DynamoDB Schema Extension
The existing events table already had a flexible schema. Payment records are stored as a list attribute under payments on each event item:
{
"event_id": "2025-03-15-sailing-lesson",
"event_name": "Saturday Sailing Lesson",
"payments": [
{
"timestamp": "2025-03-10T14:22:00Z",
"patron_name": "Jane Doe",
"amount": 45.00,
"notes": "Cash payment received",
"logged_by": "admin@example.com"
}
],
...
}
This design avoids schema migration overhead and keeps payment records co-located with event data, simplifying queries and updates.
Frontend UI: Payment Modal
The dispatch HTML at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html was extensively updated (many iterations to get the modal structure and styling correct). A new modal was added that:
- Appears when crew clicks a "Log Payment" button on an event card
- Contains form fields for patron name, amount (currency input), optional notes, and a date picker defaulting to today
- Submits to
/api/payment/logvia the existingapiFetchhelper with proper auth headers - Displays success/error feedback to the user and refreshes the event list on success
- Follows the existing modal pattern in the codebase (CSS class
activefor display, rather than inline styles)
Infrastructure Changes
Lambda Deployment
The updated Lambda code was packaged and deployed to the existing Function URL. Steps included:
- Building a zip file with the modified
lambda_function.py - Pulling the current environment variables from the Lambda config, merging in the Gmail/SES credentials (which were already present from prior setup), and verifying no secrets were lost
- Deploying the code zip via the AWS Lambda API
- Waiting for the code deployment to settle before testing
CloudFront Routing Fix (Diagnosed, Not Yet Fixed)
Investigation revealed a routing bug where waiver page requests (e.g., /g/2026-05-23/waiver) were falling through to CloudFront's S3 origin instead of being routed to the Lambda Function URL. The symptom was a "Could not load event" error in the browser, caused by the dispatch SPA trying to parse the path as an event_id and fetching from the wrong endpoint.
Root cause: CloudFront behavior for /g/*/waiver was routing to S3, but the Lambda at lambda_function.py:1697 has a working handle_waiver_get that returns HTML.
Fix (to be applied in next session): Add a CloudFront behavior with the pattern /g/*/waiver that routes to the Lambda Function URL origin, prioritizing it before the default S3 behavior.
Key Decisions
- Payment records in DynamoDB events table: Rather than creating a separate payments table, we stored payments as a list attribute on each event. This trades off normalization for simplicity and avoids cross-table queries; payment volumes per event are expected to be small (dozens at most).
- Modal display via CSS class: Consistent with the existing codebase, the modal uses an
activeCSS class anddisplay: none / blockin the stylesheet, rather than inline style manipulation. This keeps styling logic in one place and makes the DOM easier to inspect. - Email credential helpers with fallback: Given that SES/Gmail credentials may not always be populated in all environments, the helpers validate and gracefully degrade rather than throwing errors, allowing the payment logging feature to work independently of email sending.
- Admin token validation in payment handler: Following the existing auth pattern in the Lambda, payment logging requires a valid admin token in the request, preventing unauthorized logging.
Testing & Validation
- Smoke-tested the new
/api/payment/logendpoint with an admin token, verifying 401 (unauthorized) responses for missing/invalid tokens and 200 (success) for valid requests - Verified staging deployment of the updated dispatch HTML and confirmed the new modal renders and submits without JS errors
- Checked Lambda error logs for any issues during code and config updates
What's Next
- CloudFront routing fix: Add the
/g/*/waiverbehavior to the CloudFront distribution to route waiver requests to Lambda, not S3 - Event slug validation: The event ID
2026-05-23violates the naming convention (should beYYYY-MM-DD-firstname-[period] - Payment history UI: Display logged payments on event cards (read-only or with edit/delete for admins)
- Reconciliation reports: Query all events and aggregate payment totals for accounting purposes
Staging URL ready for testing at: https://tech.queenofsandiego.com/_staging/