```html

Debugging and Restoring the Adam Cherry Comics Stripe Checkout Flow: From Hosted Redirect to Embedded Modal

Overview

During a development session on the adamcherrycomics.dangerouscentaur.com site, we discovered that the Stripe checkout implementation had drifted from its documented state. The handoff notes claimed a hosted-redirect flow, but the Lambda was actually returning PaymentIntent client secrets for an embedded modal—yet the frontend was broken and checkout was non-functional in production. This post covers the diagnosis, root causes, and the technical decisions made to restore a working checkout experience.

What Was Done

  • Verified all site pages returned HTTP 200 via smoke testing against staging and production
  • Inspected the live frontend HTML for Stripe initialization markers and checkout modal logic
  • Synced the production ACC repo to the development EC2 host for local analysis
  • Compared production index.html against the local EC2 source to identify divergence
  • Patched index.html to restore the missing <script src="https://js.stripe.com/v3/"> tag
  • Deployed the patched index.html to the ACC staging S3 bucket and invalidated the CloudFront distribution
  • Ran Playwright smoke tests against staging to verify the checkout modal flow
  • Diagnosed CORS preflight failures in the Lambda checkout handler
  • Patched the Lambda checkout.py to derive RETURN_URL from the request origin and support multi-origin CORS
  • Configured API Gateway CORS rules to accept the staging origin
  • Re-ran browser smoke tests and verified CloudWatch Lambda logs
  • Promoted staging changes to production and verified end-to-end

Technical Details: The Root Cause

The index.html file was missing the critical Stripe.js library include tag. Without <script src="https://js.stripe.com/v3/"></script>, the browser had no access to the Stripe client-side APIs needed to initialize the Payment Element or handle the embedded checkout modal. The Lambda was correctly minting PaymentIntent client secrets, but the frontend had no way to consume them.

Additionally, the Lambda's RETURN_URL was hardcoded to a single origin. When requests came from the staging environment or different origins, the CORS preflight would fail because:

  • The Lambda's Access-Control-Allow-Origin header only matched one hardcoded domain
  • The API Gateway CORS configuration was not aligned with the Lambda's allowed origins
  • The RETURN_URL was locked to production, so Stripe redirects would point to the wrong environment

Infrastructure: File and Resource Locations

The Adam Cherry Comics deployment uses the following architecture:

  • S3 Bucket: dc-sites (shared across multiple DC subdomain sites)
  • CloudFront Distribution ID: E2Q4UU71SRNTMB
  • API Gateway: n0nh1zscq4
  • Lambda Function: adam-cherry-checkout (Python 3.x runtime)
  • AWS Profile: finalconstructclean
  • S3 Path: s3://dc-sites/adamcherrycomics/index.html and associated assets
  • Domain: adamcherrycomics.dangerouscentaur.com (CNAME record at Namecheap, not apex domain)

Key Technical Decisions

1. Restoring Stripe.js Initialization

The missing <script> tag was the first critical fix. We added it to the <head> section of index.html:

<script src="https://js.stripe.com/v3/"></script>

This must be loaded early, before any inline modal scripts that call Stripe() or create PaymentElement instances. Stripe.js is also cached aggressively by their CDN, so there's no performance penalty for always including it.

2. Dynamic Origin Detection in Lambda

Rather than hardcoding RETURN_URL, we modified the Lambda handler to extract the origin from the incoming HTTP request:

origin = event['headers'].get('origin') or event['headers'].get('Origin')
RETURN_URL = f"{origin}/checkout-success"

This allows the same Lambda function to work seamlessly with staging and production environments. The PaymentIntent is created with a success_url parameter pointing to the correct environment.

3. Multi-Origin CORS in Lambda

The Lambda response headers now use:

'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'

This allows the OPTIONS preflight from the browser to succeed when the frontend is on a different domain (e.g., staging vs. prod).

4. API Gateway CORS Configuration

We added explicit CORS rules to the API Gateway resource pointing to the Lambda:

  • Allowed Origins: https://adamcherrycomics.dangerouscentaur.com + staging origin
  • Allowed Methods: GET, POST, OPTIONS
  • Allowed Headers: Content-Type, Authorization

Both the Lambda and API Gateway must agree on CORS rules; if they conflict, the browser will reject the request.

5. Embedded Modal vs. Hosted Redirect

The site is using ui_mode="embedded" with Stripe's Payment Element, not the hosted checkout redirect. This was the correct choice for this implementation because:

  • Keeps the user on the site without a redirect
  • Provides better branding and UX control
  • Reduces abandonment from users navigating away during checkout

The earlier handoff note about redirect-only was outdated; the codebase already supported embedded modal correctly once Stripe.js was restored.

Deployment and Verification

After patching index.html, we:

  • Uploaded the patched version to s3://dc-sites/adamcherrycomics/
  • Invalidated the