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_secretinstead of Checkout Session URLs - Discovered mismatch between handoff documentation and actual code state
- Patched Lambda function for multi-origin CORS compliance
- Fixed Lambda
RETURN_URLderivation 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/checkoutPOST) - 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/v3script 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