Debugging and Stabilizing Stripe Embedded Checkout on adamcherrycomics.dangerouscentaur.com

Overview

During a routine handoff review and verification session for the Adam Cherry Comics site, we discovered that checkout was broken in production despite the infrastructure appearing healthy. The Lambda function was returning PaymentIntent client secrets instead of properly initializing Stripe's embedded checkout modal on the frontend. This post covers the diagnosis, the root cause (missing Stripe.js script tag), and the fixes applied across the frontend, Lambda, and infrastructure layers.

What Was Done

1. Verification and Diagnosis

We started by verifying the live site status:

  • All pages at https://adamcherrycomics.dangerouscentaur.com/ returned HTTP 200 ✅
  • Invoked Lambda function adam-cherry-checkout directly and received a PaymentIntent client_secret response
  • Inspected live frontend HTML for Stripe initialization markers
  • Grepped the live index.html for Stripe() and openCheckout calls

The critical finding: the <script src="https://js.stripe.com/v3/"> tag was missing from the live index.html, which meant the frontend had no Stripe library loaded and could not process the PaymentIntent or mount the embedded checkout modal.

2. Frontend Patch: Restore Stripe.js and Modal Initialization

We created /tmp/patch_acc_checkout.py to programmatically inject the missing script tag and verify the checkout modal initialization code was present.

Changes made to index.html:

  • Inserted <script src="https://js.stripe.com/v3/"></script> in the document head
  • Verified the modal JS block containing Stripe(publishable_key) initialization was intact
  • Confirmed the openCheckout() function was properly wired to the buy buttons
  • Validated that the modal HTML structure included the <div id="payment-element"> container

Deployed patched index.html to s3://dc-sites/adamcherrycomics.dangerouscentaur.com/index.html and invalidated CloudFront distribution E2Q4UU71SRNTMB.

3. Lambda CORS and Origin Handling

Initial browser smoke tests against staging failed with CORS preflight errors. The Lambda function at /projects/adamcherrycomics.md (source on EC2 in the ACC repo) had hardcoded RETURN_URL and did not dynamically derive the origin from the incoming request.

Lambda patches applied:

  • Modified checkout.py handler to extract the origin from the Origin request header
  • Updated RETURN_URL to be computed at runtime: origin + "/checkout-success" instead of hardcoded domain
  • Added explicit Access-Control-Allow-Origin response header with the request's origin value
  • Configured API Gateway CORS to permit POST /checkout from https://adamcherrycomics.dangerouscentaur.com

Lambda environment was rebuilt and redeployed to adam-cherry-checkout via the configured CI/CD pipeline.

4. Stripe Embedded Checkout Configuration

The Lambda function was already using ui_mode="embedded" (contrary to the initial handoff notes), which is the correct configuration per the platform's modal-only policy. However, we needed to verify the Stripe SDK version and payment mode:

  • Confirmed stripe-python 15.1.0 was pinned in the Lambda layer
  • Verified payment_method_types=["card"] for card-only checkout
  • Validated that Checkout Session creation included success_url and cancel_url parameters derived from the request origin

5. Deployment and Testing

Created /tmp/smoke_acc_staging.py using Playwright to run end-to-end browser tests:

  • Load product page on staging
  • Click buy button for a $10 product
  • Verify modal appears and Stripe.js iframe loads
  • Inspect CloudWatch Logs for Lambda invocation and any errors
  • Verify PaymentIntent returns with client_secret

After each fix (Stripe.js injection, CORS, Lambda return URL), re-ran smoke tests and tailed CloudWatch logs in real time to catch any remaining issues.

6. Mount-Clear and Cache Handling

Created /tmp/patch_mount_clear.py to ensure staging and prod S3 mount points were properly cleared before deployment, preventing stale index.html from being served. This was especially critical because CloudFront caching and S3 object replication required explicit cache invalidation after the patch.

Cache invalidation steps:

  • Created CloudFront invalidation for /* on distribution E2Q4UU71SRNTMB
  • Waited for invalidation to complete (typically 30–60 seconds)
  • Performed cache-busting fetch: curl -H "Cache-Control: no-cache" https://adamcherrycomics.dangerouscentaur.com/index.html
  • Verified version header and asset timestamps in response

Technical Details and Architecture

Stripe Embedded Checkout Flow

The corrected flow is now:

  1. User clicks "Buy" button on a product (e.g., $10 print)
  2. Frontend calls openCheckout(product_id, price)
  3. Frontend POST to https://n0nh1zscq4.execute-api.us-east-1.amazonaws.com/checkout with product metadata
  4. Lambda adam-cherry-checkout creates Stripe Checkout Session with ui_mode="embedded"
  5. Lambda returns { "clientSecret": "cs_..." }
  6. Frontend calls stripe.confirmPayment({ clientSecret, elements })
  7. Stripe renders payment form inside the modal <div id="payment-element">
  8. User enters card and completes payment
  9. Stripe redirects to success_url derived from request origin

Infrastructure Stack

  • S3 bucket: dc-sites (multi-site bucket with prefix adamcherrycomics.dangerouscentaur.com