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 fromindex.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:
- Initial Migration (handoff era): The Stripe.js script tag was removed from
index.htmlduring an attempted transition to a different checkout pattern, but the removal was never completed or reverted. - Lambda Configuration: The Lambda function at
/tmp/checkout_connect.pywas patched to useui_mode="embedded"and return PaymentIntent secrets, but the frontend was never updated to consume this correctly. - CORS and Origin Handling: Multiple patches to
/tmp/patch_lambda_cors.pywere 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-Origindynamically based on theOriginheader - Ensuring
Access-Control-Allow-Methodsincludes 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-sitesbucket contains the staticindex.html, product images, and CSS - CloudFront Distribution:
E2Q4UU71SRNTMBcaches and serves the site globally - API Gateway:
n0nh1zscq4exposes the Lambda function at/checkout - Lambda Function:
adam-cherry-checkouthandles Stripe PaymentIntent creation and CORS logic - AWS Profile:
finalconstructcleanholds credentials for all deployments - DNS: Namecheap hosts the CNAME record for
adamcherrycomics.dangerouscentaur.compointing 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:
- Load the site and verify all product pages return HTTP 200
- Click the checkout button and verify the Stripe modal initializes
- Confirm the PaymentIntent client_secret is returned and mounted in the Stripe Elements form
- 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