```html

Debugging and Fixing Stripe Embedded Checkout for adamcherrycomics.dangerouscentaur.com

During a recent development session, we discovered that the Stripe checkout flow for Adam Cherry Comics was broken in production despite the handoff notes claiming it was working. This post documents the investigation, root causes, and fixes applied to restore payment functionality.

What Was Done

  • Verified all site pages return HTTP 200 across production
  • Identified that Lambda was returning PaymentIntent client_secret instead of Checkout Session URLs
  • Discovered mismatch between handoff documentation and actual code state
  • Patched Lambda function for multi-origin CORS compliance
  • Fixed Lambda RETURN_URL derivation from request origin
  • Tested and promoted fixes through staging to production
  • Identified lingering infrastructure cleanup tasks

Technical Details: The Checkout Flow Investigation

Initial State Discovery

The site appeared functional on the surface—all pages returned 200 status codes, and the site was live at https://adamcherrycomics.dangerouscentaur.com/. However, testing the checkout Lambda function revealed the first anomaly:

Lambda: adam-cherry-checkout (API Gateway n0nh1zscq4)
Returns: PaymentIntent client_secret (pi_...)
Expected: Stripe Checkout Session redirect URL

The handoff notes claimed the site had been migrated to hosted redirect checkout, but the actual Lambda code was using ui_mode="embedded"—returning a PaymentIntent instead of a Session. This is a critical distinction: embedded mode requires client-side JavaScript to mount the Stripe Payment Element, while hosted redirect mode provides a URL for server-side redirect.

Frontend Modal Analysis

We inspected the live index.html snapshot pulled from the S3 production bucket (dc-sites) and found:

  • Modal JavaScript expected to call the Lambda at /checkout
  • Missing <script src="https://js.stripe.com/v3/"></script> tag (handoff notes indicated this was removed but shouldn't have been)
  • No Stripe Payment Element initialization code to mount the embedded checkout
  • Modal JS was trying to redirect to a URL, not initialize an embedded Payment Element

This was the root cause: the Lambda was correctly configured for embedded mode, but the frontend had no code to handle it, and the critical Stripe.js library tag was missing entirely.

Lambda CORS and Return URL Fixes

Multi-Origin CORS Problem

Initial smoke tests showed preflight requests failing. The Lambda had hard-coded return URLs and CORS headers that only recognized a single origin. Since the site is served from CloudFront distribution E2Q4UU71SRNTMB, we needed the Lambda to accept requests from both staging and production origins.

File modified: /tmp/patch_lambda_cors.py

# Before: Hard-coded origin in Lambda
CORS_ORIGIN = "https://adamcherrycomics.dangerouscentaur.com"

# After: Derive from request
origin = event.get("headers", {}).get("Origin", "")
allowed_origins = [
    "https://adamcherrycomics.dangerouscentaur.com",
    "https://acc-staging.dangerouscentaur.com"
]
cors_origin = origin if origin in allowed_origins else allowed_origins[0]

RETURN_URL Derivation

The checkout flow must redirect back to the correct domain after payment. We patched the Lambda to derive RETURN_URL from the request origin instead of hard-coding it:

# In lambda_function.py, checkout handler
request_origin = event["headers"].get("Origin", "https://adamcherrycomics.dangerouscentaur.com")
return_url = f"{request_origin}/checkout-success"

# Creates dynamic return URLs:
# Staging: https://acc-staging.dangerouscentaur.com/checkout-success
# Prod:    https://adamcherrycomics.dangerouscentaur.com/checkout-success

API Gateway CORS Configuration

Even with Lambda-level CORS headers, API Gateway n0nh1zscq4 also needed CORS configuration to handle preflight OPTIONS requests. We verified and updated the API Gateway resource at the root path to include both origins in the Access-Control-Allow-Origin header.

Testing and Promotion

Smoke Test Strategy

We used Playwright for automated checkout flow verification:

File: /tmp/smoke_acc_staging.py
Tests:
1. Navigate to checkout modal
2. Verify Stripe Payment Element mounts
3. Verify preflight requests succeed (no 403)
4. Confirm client_secret returned from Lambda
5. Verify return URL matches origin

CloudFront Cache Invalidation

After deploying fixes to staging, CloudFront distribution E2Q4UU71SRNTMB needed cache invalidation. Browser caching was masking the new fixes, so we invalidated the path:

aws cloudfront create-invalidation \
  --distribution-id E2Q4UU71SRNTMB \
  --paths "/*"

Infrastructure Overview

  • Site Bucket: s3://dc-sites (multi-tenant, contains adamcherrycomics content)
  • CloudFront Distribution: E2Q4UU71SRNTMB (origin: dc-sites S3)
  • Lambda Function: adam-cherry-checkout (Python runtime, invoked via API Gateway)
  • API Gateway: n0nh1zscq4 (endpoint for /checkout POST)
  • AWS Profile: finalconstructclean
  • DNS: Namecheap CNAME record adamcherrycomics → CloudFront domain (wildcard alone was being shadowed)

Key Decisions

  • Embedded vs. Redirect: We kept the embedded checkout approach because it provides a better UX (no redirect, no page load interruption) and aligns with your global pattern of embedded Stripe modals. The hosted redirect mode noted in handoff was aspirational, not actual.
  • Multi-Origin CORS: We made the Lambda origin-agnostic to support staging and production from a single code path, reducing deployment complexity.
  • Client-Side Implementation: Adding back the missing js.stripe.com/v3 script and Payment Element init code to the modal JS was mandatory—without it, the embedded checkout couldn't render.

What's Next

Several items remain:

  • Adam Confirmation: No end-to-end confirmation from Adam that the restored checkout works. Once tested, we