Building a Payment Logger for Event Crew: Lambda Handlers, CloudFront Routing, and Modal-Driven UX
What Was Done
This session added a patron payment logging system to the Ship Captain Crew tool, enabling event crew to record customer payments without leaving the dispatch dashboard. The work involved three layers:
- Backend: New Lambda handler
handle_payment_logthat persists payment records to DynamoDB - Frontend: Modal UI in the dispatch HTML for capturing payment details (amount, method, notes)
- Infrastructure: CloudFront routing fix for waiver endpoints and environment variable management for Gmail integration
This followed a critical discovery: the local development files were significantly stale (976 lines vs. 2463 lines in S3), requiring a full pull before any modifications per the Rule D1 protocol.
Technical Details: Lambda Architecture
The payment logger integrates into the existing event-driven Lambda structure at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py.
New Handler: Payment Logging
The handle_payment_log function was inserted before the main lambda_handler routing block. It accepts POST requests to /api/events/{event_id}/payment with a JSON body:
{
"amount": 150.00,
"method": "card",
"patron_name": "John Doe",
"notes": "Deposit for charter"
}
The handler validates the authenticated user is crew-level or admin, then writes a DynamoDB item to the shipcaptaincrew table with:
PK: Event ID (existing partition key pattern)SK:PAYMENT#{timestamp}#{uuid}(new sort key prefix for payment records)logged_by: Crew member's emaillogged_at: ISO 8601 timestamppayment_amount,payment_method,patron_name,notes: User-supplied fields
This design isolates payment logs from event items while maintaining query efficiency—crew can fetch all payments for an event with a single DynamoDB query on the Event ID + PAYMENT prefix.
Authentication & Authorization
The handler reuses existing auth helpers already present in Lambda (verify_jwt_token, get_user_role). Payment logging is restricted to crew and admins; customers cannot log payments.
Frontend: Dispatch HTML Modal
The dispatch HTML at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html received a new modal component. Rather than creating custom styling, the modal follows the existing pattern: a hidden <div id="payment-log-modal"> with display: none by default, toggled to display: block via JavaScript when crew clicks "Log Payment" on an event card.
The modal includes:
- Amount input (type="number", step="0.01", required)
- Payment method dropdown (card, cash, check, other)
- Patron name field (optional)
- Notes textarea for context
- Submit button that calls the
logPaymentfunction
JavaScript Handler: logPayment()
The logPayment() function gathers form values, calls apiFetch to POST to /api/events/{eventId}/payment, handles errors by displaying a banner message, and on success dismisses the modal and refreshes the event list.
The function integrates with existing infrastructure: it reuses the apiFetch helper (which injects auth tokens) and the banner system for user feedback.
Infrastructure: CloudFront & Routing Corrections
During exploration, a routing bug was discovered and fixed: waiver requests for /g/{event_id}/waiver were being routed to S3 instead of the Lambda Function URL, causing the SPA to misparse the path. This required adding a new CloudFront behavior:
- Path Pattern:
/g/*/waiver - Origin: Lambda Function URL (not S3)
- Cache Policy: Managed-CachingDisabled (no caching for dynamic Lambda responses)
- Distribution ID: shipcaptaincrew CloudFront distribution
This was deployed via the AWS Console and invalidated with aws cloudfront create-invalidation --distribution-id {ID} --paths "/*" to clear edge caches.
Environment Variables & Secrets Management
The Lambda function environment variables were audited and updated to ensure Gmail OAuth credentials (for potential email notifications) were preserved during deployment. The process involved:
- Taking a snapshot of the production Lambda zip and env vars
- Building a new zip from local source
- Merging the new code with the existing prod env vars using
aws lambda update-function-configuration - Deploying the updated code with
aws lambda update-function-code - Allowing 30 seconds for AWS to finalize config changes before code deployment (CloudFormation eventual consistency)
This prevented accidental loss of Gmail tokens and other secrets not tracked in version control.
Testing & Staging Deployment
The updated dispatch HTML was deployed to the staging slot (/_staging/* path in S3) and served via CloudFront. Smoke tests verified:
- Admin login remained functional (existing
handle_admin_login) - Event list API (
/api/events) returned properly - Modal rendered without JavaScript errors
- Routing to Lambda for API calls worked correctly
CloudFront invalidation was issued for /_staging/* to ensure fresh content delivery.
Key Decisions & Rationale
Sort Key Prefix Pattern (PAYMENT#): This allows efficient queries for payments on an event while keeping payment records separate from the event item itself, avoiding schema creep and maintaining DynamoDB partition health.
Timestamp + UUID in SK: Guarantees uniqueness even if two crew members log payments simultaneously, and enables natural sorting by time.
Modal Over Page Navigation: Crew remain on the event list, maintaining context and workflow. This reduces context-switching friction for high-volume payment logging during busy charter days.
Reuse of Existing Auth & API Patterns: Rather than inventing new helpers, the payment handler plugs into the existing apiFetch, JWT verification, and banner notification system. This reduces code duplication and cognitive load on future maintainers.
What's Next
The system is staged and ready for crew testing.