Integrating Gmail-Backed Payment Logging into a Serverless Event Management System
This session focused on adding payment logging capabilities to the Ship Captain Crew tool—a serverless event management system running on AWS Lambda with a CloudFront-distributed SPA frontend. The challenge: enable staff to log patron payments via email confirmation, with audit trails persisted to DynamoDB and routed through Gmail's API.
What Was Done
We implemented a two-tier payment logging system:
- Backend: Added Gmail token handling and a new
handle_payment_loggedLambda handler to process payment events - Frontend: Built a "Log Payment" modal UI component in the dispatch SPA with form validation and API integration
- Infrastructure: Extended Lambda environment variables to safely store Gmail OAuth credentials and added a new DynamoDB event status transition pathway
The session also surfaced and documented two critical bugs for follow-up: waiver routes being incorrectly handled by CloudFront S3 fallthrough, and stale local tooling requiring refresh from the canonical S3 source.
Technical Details: Backend Implementation
The Lambda function lives at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py and is deployed as a CloudFront-backed Function URL. To support payment logging, we:
1. Added Gmail Helper Functions (pre-handler)
Inserted helper functions before the lambda_handler entry point to manage Gmail token refresh and message composition. These helpers:
- Retrieve Gmail OAuth refresh tokens from Lambda environment variables (e.g.,
GMAIL_REFRESH_TOKEN) - Exchange refresh tokens for short-lived access tokens via Google's token endpoint
- Compose and send confirmation emails to staff with payment details (amount, patron name, event ID)
- Handle token expiration gracefully with exponential backoff retry logic
2. Implemented handle_payment_logged Route Handler
Added a new route handler that processes POST requests to /api/events/{event_id}/payment. The handler:
- Extracts payment metadata from the request body (amount, method, timestamp, staff_member_id)
- Validates authentication via the existing admin token verification flow
- Appends a payment record to the event's
paymentslist in DynamoDB - Triggers Gmail notification via the helper functions
- Returns a 200 response with the updated event document
3. Extended Environment Variable Configuration
Updated the Lambda environment to include:
GMAIL_REFRESH_TOKEN— OAuth refresh token for the shared Gmail accountGMAIL_CLIENT_IDandGMAIL_CLIENT_SECRET— OAuth application credentialsGMAIL_SENDER_EMAIL— The staff notification email addressPAYMENT_LOG_RECIPIENTS— Comma-separated list of staff emails to notify
These were merged into the existing environment payload and deployed via the Lambda config update (no code redeploy required for future changes).
Technical Details: Frontend Implementation
The dispatch SPA is served from S3 at the shipcaptaincrew bucket root, invalidated through CloudFront distribution. The HTML file (index.html) received extensive iteration to add payment logging UI:
1. Modal Structure
Added a new modal component following the existing modal pattern in the codebase. The modal includes:
- Amount input field with currency formatting
- Payment method selector (card, check, cash, other)
- Optional notes textarea for staff comments
- Submit and cancel buttons with loading state management
The modal is hidden by default and toggled via an "active" CSS class applied to its container element—consistent with the existing dashboard and event detail modals.
2. Event Card Integration
Updated the event card render function to include a "Log Payment" button that:
- Only appears for staff with admin-level access (checked via token scope)
- Triggers the modal with the current event ID pre-filled
- Disables further clicks while a request is in flight
3. API Integration
Leveraged the existing apiFetch helper to POST payment data to /api/events/{event_id}/payment. The fetch handler:
- Constructs the Authorization header with the stored admin token
- Catches HTTP errors (401 Unauthorized, 403 Forbidden, 500 errors) and displays friendly error messages
- Re-renders the event card on success to show the updated payment total
- Clears the form and closes the modal on successful submission
Infrastructure & Deployment
DynamoDB Table Schema
The existing event items in the shipcaptaincrew-events table were extended with a new payments list attribute. Each payment record follows this shape:
{
"amount": 75.50,
"method": "card",
"timestamp": "2025-01-15T14:32:10Z",
"staff_member_id": "cb@example.com",
"notes": "Logged via dispatch portal"
}
Lambda Deployment Process
The deployment followed this sequence:
- Syntax validation of the updated
lambda_function.py - Creation of a deployment ZIP file containing the updated handler
- Merged environment variable payload (preserving existing vars while adding Gmail config)
- Push to Lambda via AWS API (config update + code update, sequentially)
- Polling for configuration and code to settle (typically 5–10 seconds)
- Smoke test of the new admin endpoint with real admin token to verify routing
CloudFront & S3 Publishing
The updated index.html` was deployed to the staging slot on the shipcaptaincrew S3 bucket, then invalidated via the CloudFront distribution's cache invalidation API (path pattern: /_staging/*). A follow-up prod deployment requires explicit approval after staging validation.
Key Decisions
Why Gmail API instead of SES? The team already has Gmail tokens in the environment from prior integrations. SES would require new IAM permissions and DKIM verification; Gmail reduces credential sprawl and leverages existing infrastructure.
Why store payments in the event document rather than a separate table? Event payments are always queried alongside event metadata and staff workflow. Denormalizing into the event document avoids an additional DynamoDB query and keeps the data model aligned with the UI's single event view.
Why modal-based UI rather than a dedicated page?