```html

Building a Payment Logging System for Patron Management in a Serverless SPA

What Was Done

Added a complete payment logging pipeline to the Ship Captain Crew (SCC) event management system, enabling admins to record patron payments through a new modal interface. The system integrates AWS Lambda handlers with an HTML5 modal UI, DynamoDB persistence, and email notifications via SES.

Architecture Overview

The SCC tool is a serverless single-page application (SPA) deployed across three layers:

  • Frontend: Static dispatch HTML served from S3 bucket queenofsandiego.com-scc-dispatch through CloudFront distribution (CF dist for shipcaptaincrew)
  • Backend: AWS Lambda function at /tools/shipcaptaincrew/lambda_function.py handling all API routes
  • Data: DynamoDB table storing events, with payment records embedded in event items

The payment logging feature follows this flow: Admin clicks "Log Payment" → Modal form submits to Lambda → Lambda validates and writes to DynamoDB → Lambda sends SES email notification → Frontend receives confirmation.

Lambda Backend Implementation

Added two new handler functions to lambda_function.py before the main lambda_handler routing logic:

  • handle_payment_log(event_id, patron_name, amount, payment_method, notes) — Validates inputs, writes payment record to DynamoDB event item, returns success/error response
  • send_payment_notification(patron_name, amount, event_id) — Uses AWS SES to email admin confirmation; references environment variables for sender email and recipient list

The handlers integrate with existing Lambda infrastructure:

  • Uses existing DynamoDB table schema (payment records added as nested objects in event items)
  • Respects admin authentication via existing verify_admin_token() function
  • Leverages SES client already initialized for other email features
  • Follows existing error response pattern (400 for validation, 500 for service errors)

New API route added to lambda_handler routing:

POST /api/payment/log
  Required: event_id, patron_name, amount, payment_method
  Optional: notes
  Auth: Admin token required
  Returns: { "status": "success", "payment_id": "...", "timestamp": "..." }

The handler validates:

  • Event exists in DynamoDB
  • Amount is positive numeric value
  • Payment method is one of: ["cash", "card", "check", "venmo"]
  • Patron name is non-empty string

Frontend Modal Interface

Modified /tools/shipcaptaincrew/index.html to add payment logging UI:

  • New modal HTML inserted near existing modals (follows existing modal-active class pattern for visibility toggling)
  • Form inputs for: patron name, amount, payment method dropdown, optional notes field
  • Submit button triggers logPayment() JavaScript function
  • "Log Payment" button added to event card context menu (visible only for admins)

JavaScript implementation:

async function logPayment(eventId, formData) {
  const response = await fetch('/api/payment/log', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${adminToken}`
    },
    body: JSON.stringify({
      event_id: eventId,
      patron_name: formData.name,
      amount: parseFloat(formData.amount),
      payment_method: formData.method,
      notes: formData.notes || null
    })
  });
  
  if (response.ok) {
    const result = await response.json();
    showNotification(`Payment logged for ${formData.name}: $${formData.amount}`);
    closePaymentModal();
    refreshEventCard(eventId);
  } else {
    showError('Failed to log payment');
  }
}

Infrastructure & Deployment

DynamoDB Schema: Payment records stored as array within existing event items:

{
  "event_id": "2026-05-23-corporate-event",
  "event_name": "Corporate Mixer",
  "payments": [
    {
      "payment_id": "pay_1234567890",
      "patron_name": "John Smith",
      "amount": 150.00,
      "payment_method": "card",
      "notes": "CC ending in 4242",
      "logged_by": "admin@queenofsandiego.com",
      "timestamp": "2026-01-15T14:32:00Z"
    }
  ]
}

Lambda Environment Variables: No new env vars required; uses existing:

  • SES_SENDER_EMAIL — Verified SES sender for notifications
  • ADMIN_NOTIFY_EMAILS — Comma-separated list of admin emails (updated during deployment)

Deployment Process:

  • Update lambda_function.py with new handlers
  • Create deployment zip: zip -r lambda.zip lambda_function.py
  • Deploy to Lambda: aws lambda update-function-code --function-name shipcaptaincrew --zip-file fileb://lambda.zip
  • Update dispatch HTML in S3: aws s3 cp index.html s3://queenofsandiego.com-scc-dispatch/index.html
  • Invalidate CloudFront cache: aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"

Staging Validation: Before production deployment, changes are pushed to staging slot:

  • HTML deployed to s3://queenofsandiego.com-scc-dispatch/_staging/index.html
  • CloudFront behavior routes /_staging/* to staging bucket
  • Admin tests payment logging at staging URL before production cutover

Key Technical Decisions

Payment Records as Nested Arrays: Rather than creating a separate DynamoDB table for payments, we embed payment records within event items. This keeps related data together, simplifies queries, and avoids additional partition key complexity.

SES for Notifications: Email notifications use existing SES integration rather than introducing a new messaging service. This reduces infrastructure surface area and leverages already-configured sender identity.

Admin-Only Access: Payment logging requires existing admin token authentication. No new auth mechanism was introduced; we reuse the established token verification pattern.

Modal UX Pattern: Payment modal follows existing UI conventions in the codebase (class-based visibility toggling, consistent form styling, standard error/success feedback).