Debugging and Stabilizing the AdamCherryComics Stripe Embedded Checkout Flow

During a development session focused on the adamcherrycomics.dangerouscentaur.com e-commerce site, we discovered that the Stripe payment flow was broken in production despite handoff documentation claiming it was fully operational. This post walks through the diagnosis, root cause analysis, and the infrastructure decisions made to restore and standardize the checkout experience.

The Problem: Discrepancy Between Handoff and Reality

The handoff document stated that Adam Cherry Comics had been migrated from a "DM to Buy" workflow to a Stripe-hosted redirect checkout. However, when we verified the live Lambda function (adam-cherry-checkout) and inspected the frontend modal JavaScript, we found:

  • The Lambda was returning a Stripe PaymentIntent client_secret (format: pi_...) rather than a Checkout Session URL
  • The Lambda was already configured with ui_mode="embedded", contradicting the handoff statement about redirect flow
  • The frontend modal JavaScript expected to mount an embedded Stripe Elements form, not redirect
  • The critical <script src="https://js.stripe.com/v3/"> tag was missing from index.html, preventing Stripe.js from loading at all

This mismatch meant checkout was silently failing: the page would attempt to initialize Stripe without the library loaded, resulting in a JavaScript error and no payment form rendering.

Root Cause: Multiple Layers of Incomplete Patching

Tracing through the session logs revealed the issue arose from sequential partial fixes:

  1. Initial Migration (handoff era): The Stripe.js script tag was removed from index.html during an attempted transition to a different checkout pattern, but the removal was never completed or reverted.
  2. Lambda Configuration: The Lambda function at /tmp/checkout_connect.py was patched to use ui_mode="embedded" and return PaymentIntent secrets, but the frontend was never updated to consume this correctly.
  3. CORS and Origin Handling: Multiple patches to /tmp/patch_lambda_cors.py were applied to handle multi-origin requests from staging and production, but the underlying integration remained incomplete.

Technical Fixes Applied

1. Restore Stripe.js Script Tag

The first fix was to add back the missing Stripe library initialization in the frontend. We modified the live index.html hosted in S3 bucket dc-sites to include:

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

This was inserted in the <head> section before any modal JavaScript attempted to reference the Stripe object. This is a critical dependency—without it, all subsequent checkout initialization fails silently.

2. Verify and Patch Lambda CORS Headers

The Lambda function needed to handle preflight requests from two origins:

  • Production: https://adamcherrycomics.dangerouscentaur.com
  • Staging: The same domain (no separate staging domain was in use)

We inspected the Lambda environment variables and handler logic in /tmp/patch_lambda_cors.py, which derives the RETURN_URL from the incoming request origin rather than hardcoding it. This allows the Lambda to correctly redirect back to the calling site after Stripe processes the payment.

The fix involved:

  • Setting Access-Control-Allow-Origin dynamically based on the Origin header
  • Ensuring Access-Control-Allow-Methods includes OPTIONS for preflight handling
  • Configuring API Gateway to pass through OPTIONS requests without authentication

3. Fix Mount-Clear Cache Issues

During testing, CloudFront was caching stale versions of index.html after deployments. We applied the fix in /tmp/patch_mount_clear.py to invalidate the CloudFront distribution (ID: E2Q4UU71SRNTMB) after each S3 update.

The invalidation pattern used was /* to clear all paths, though in production this should be scoped to specific paths if the CDN serves high-traffic content:

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

Infrastructure Overview

The AdamCherryComics infrastructure spans multiple AWS services:

  • S3 Origin: dc-sites bucket contains the static index.html, product images, and CSS
  • CloudFront Distribution: E2Q4UU71SRNTMB caches and serves the site globally
  • API Gateway: n0nh1zscq4 exposes the Lambda function at /checkout
  • Lambda Function: adam-cherry-checkout handles Stripe PaymentIntent creation and CORS logic
  • AWS Profile: finalconstructclean holds credentials for all deployments
  • DNS: Namecheap hosts the CNAME record for adamcherrycomics.dangerouscentaur.com pointing to the CloudFront distribution

Testing Strategy: Playwright Smoke Tests

We created a Playwright-based smoke test (/tmp/smoke_acc_staging.py) to validate the full checkout flow:

  1. Load the site and verify all product pages return HTTP 200
  2. Click the checkout button and verify the Stripe modal initializes
  3. Confirm the PaymentIntent client_secret is returned and mounted in the Stripe Elements form
  4. Validate CORS preflight OPTIONS requests succeed

Tests were run iteratively against staging before promoting to production, with CloudWatch logs tailed in parallel to catch Lambda errors.

Key Decisions and Trade-Offs

Decision: Embedded Checkout (PaymentIntent) vs. Hosted Redirect (Checkout Sessions)

The Lambda was already configured to use PaymentIntent with embedded checkout. While this requires more frontend complexity (mounting the Stripe Elements form), it provides:

  • Better UX (no redirect out of site)
  • Full control over styling and flow
  • Compliance with the organization's global policy of embedded modals, never redirects

Earlier handoff attempts to use `stripe-python 15.1.0` with `ui_mode="embedded"` had failed, but this was likely due to a version compatibility issue rather than a fundamental limitation. The current setup uses PaymentIntent directly, byp