Implementing Payment Logging for Patrons: Lambda Handler Architecture and CloudFront Routing Fixes
During this development session, we built out payment logging infrastructure for the Ship Captain Crew event management tool while uncovering and documenting critical routing issues in the CloudFront distribution. This post details the architectural decisions, infrastructure changes, and the diagnostic framework we established for future debugging.
Session Scope and Diagnostic Discovery
The core request was straightforward: add the ability to log payments for patrons in the ShipCaptainCrew system. However, reconnaissance revealed several infrastructure gaps that needed addressing first:
- The local dispatch HTML at
/tools/shipcaptaincrew/index.htmlwas stale (976 lines vs. 2463 lines in S3), requiring a refresh before modifications - CloudFront routing for waiver paths (
/g/*/waiver) was incorrectly falling through to S3 instead of invoking the Lambda function - Event slug conventions were inconsistent (YYYY-MM-DD-firstname-[period] pattern not enforced)
Following Rule D1 (always refresh S3 state before local edits), we pulled the current production HTML from the S3 bucket and ran a full diff before proceeding with feature development.
Architecture: Gmail Token Retention and Payment Handler Pattern
The Lambda function at /tools/shipcaptaincrew/lambda_function.py required two major additions:
1. Gmail Credential Helpers
We added helper functions to manage Gmail OAuth tokens as environment variables, allowing the SES-backed email notification system to authenticate with Gmail for sending payment confirmation emails. The helpers handle:
- Token refresh logic with expiration checking
- Secure storage of credentials in Lambda environment variables (mirrored to
lambda_function.pyvia deployment config) - Graceful fallback when tokens are missing or expired
These were inserted before lambda_handler to ensure availability across all route handlers. The environment variable payload was merged from production to preserve existing Gmail token state during the Lambda code update.
2. Payment Logging Handler
A new handler handle_payment_log was designed to accept payment data from the dispatch UI and persist it to DynamoDB. The handler:
- Validates authentication via admin token verification (existing pattern)
- Extracts
event_id,patron_name,amount, andpayment_methodfrom the request - Updates the corresponding event record with
payment_cleared: trueand a timestamp - Triggers an SES email notification via the Gmail helpers
- Returns a structured response with the updated event snapshot
This follows the existing routing pattern in lambda_handler where path segments are parsed from the event and dispatched to specific handlers.
Frontend: Payment Modal and Card Integration
The dispatch HTML received a new "Log Payment" modal component integrated into the event card rendering system. The modal:
- Displays when an admin clicks a payment button on an event card
- Uses the existing
apiFetchhelper to POST to/api/payment-log - Follows the established modal display pattern (active class toggle rather than inline styles)
- Includes form validation for amount and payment method selection
- Updates the event card state immediately upon success, removing the payment button
The card render function was extended to include a conditional payment button that appears only when payment_cleared === false. This leverages the same field-level conditional rendering used elsewhere in the dispatch HTML.
Infrastructure and Deployment Changes
Lambda Configuration
The Lambda function URL for ShipCaptainCrew is already configured at the AWS Lambda console. Deployment involved:
- Building a new deployment zip from
/tools/shipcaptaincrew/lambda_function.pywith the new handlers included - Merging environment variables from production (preserving Gmail tokens and admin password hash) with new payment-related config
- Using the AWS CLI to update both the function code and configuration in a single batch
- Waiting for both updates to settle before testing (CloudWatch Logs shows ~2–3 second lag)
Example deployment flow (credentials omitted):
zip -r lambda_deployment.zip lambda_function.py
# Snapshot prod state
aws lambda get-function --function-name shipcaptaincrew-prod > prod_snapshot.json
# Update code
aws lambda update-function-code --function-name shipcaptaincrew-prod --zip-file fileb://lambda_deployment.zip
# Update config with merged env vars
aws lambda update-function-configuration --function-name shipcaptaincrew-prod --environment Variables={...}
CloudFront Routing Fixes (Documented)
While not implemented in this session, we documented the required fix for the waiver routing issue:
- Add a new CloudFront behavior: Path Pattern
/g/*/waiver→ Origin: ShipCaptainCrew Lambda Function URL - This must come before the catch-all
/behavior that routes to the dispatch SPA - Current behavior: CloudFront routes
/g/2026-05-23/waiverto S3, which returns dispatch HTML, which tries to parse "2026-05-23/waiver" as an event_id and fails
Testing and Validation
Smoke tests verified:
- Admin authentication still works via existing token validation
- New
/api/payment-logendpoint returns 401 when unauthenticated (correctly wired) - Event list API (
/api/events) returns the expected schema with the newpayment_clearedfield - Staging deployment was tested via CloudFront invalidation of
/_staging/*before production push
CloudWatch Logs were monitored for errors during and after each update. The staging URL was confirmed to serve the new HTML before production deployment.
Key Architectural Decisions
Why handlers before lambda_handler? Helper functions must be defined before the main routing logic references them. This keeps the code readable and avoids forward-reference issues in Python.
Why merge environment variables? The Gmail OAuth tokens and admin password hash are secrets that only exist in production. Pulling them via get-function-configuration, merging with new config, then pushing back ensures no credentials are lost during code updates.
Why CloudFront behavior ordering matters? CloudFront evaluates behaviors in order. A catch-all behavior for the SPA must come last, or specific paths like /g/*/waiver will never reach the Lambda function.
What's Next
The payment logging feature is now ready for patron testing. An email notification will be sent to you when the feature is ready for payment logging. The next priorities are:
- Implement the CloudFront waiver routing fix to unblock
/g/*/waiverrequests - Enforce event slug naming conventions at creation time (