Debugging a Broken Stripe Embedded Checkout on adamcherrycomics.dangerouscentaur.com
Overview
During a routine handoff verification of the Adam Cherry Comics e-commerce site, we discovered that the Stripe embedded checkout flow was returning a PaymentIntent client secret instead of a proper Checkout Session URL. This mismatch between frontend expectations and Lambda backend behavior rendered the checkout modal non-functional in production. This post walks through the diagnosis, the root cause, and the remediation strategy we deployed.
What We Found
The handoff documentation indicated that the site had been migrated from a Stripe hosted-redirect flow to an embedded modal checkout. However, when we:
- Verified all pages returned HTTP 200 ✅
- Smoke-tested the checkout Lambda (
adam-cherry-checkout) - Inspected the live frontend modal JavaScript in the deployed
index.html - Probed the Lambda's actual response payload
we found a critical mismatch: the Lambda at /tmp/checkout_connect.py was correctly configured with ui_mode="embedded" and returning a PaymentIntent client secret, but the frontend expected a Checkout Session ID for the Stripe.js redirectToCheckout() flow.
Infrastructure Context
Live deployment:
- Frontend: S3 bucket
dc-sites→ CloudFront distributionE2Q4UU71SRNTMB→adamcherrycomics.dangerouscentaur.com - API layer: API Gateway
n0nh1zscq4→ Lambdaadam-cherry-checkout - AWS Profile:
finalconstructclean - DNS: Namecheap explicit CNAME record for
adamcherrycomics(not wildcard, which was being shadowed)
Products: 12 buyable items ranging from $10–$40 USD.
Root Cause Analysis
The Lambda handler in checkout_connect.py uses the current Stripe Python SDK (15.1.0+) with the correct embedded mode signature:
intent = stripe.PaymentIntent.create(
amount=amount_cents,
currency="usd",
ui_mode="embedded",
return_url=return_url,
)
This returns a PaymentIntent with a client_secret field—the correct response shape for Stripe.js v3's confirmPayment() flow.
However, the frontend modal JavaScript in the live index.html` was calling redirectToCheckout(sessionId=...), which is the old hosted-redirect API that expects a Checkout Session ID, not a PaymentIntent. This is why the checkout was broken.
Why This Happened
The Stripe Python library version 15.1.0 dropped support for the ui_mode="embedded" parameter on Checkout Sessions (it only works on PaymentIntents). The previous handoff notes claimed the site had been "migrated to redirect," but the actual code—and the correct modern pattern—uses PaymentIntents with embedded mode. The frontend code was simply never updated to match the backend implementation.
Remediation Steps Taken
1. Restore the Stripe.js library tag
The handoff notes indicated that the <script src="https://js.stripe.com/v3/"> tag had been removed from index.html. This is required for the Stripe.js v3 API used by embedded PaymentIntents. We re-inserted it in the document head.
2. Update the modal checkout JavaScript
We replaced the old redirectToCheckout() call with the modern confirmPayment() flow. The corrected pattern:
// Old (broken):
// stripe.redirectToCheckout({ sessionId: clientSecret })
// New (correct):
stripe.confirmPayment({
elements: elements,
confirmParams: {
return_url: window.location.origin + '/return',
},
redirect: 'if_required'
})
This matches the PaymentIntent client_secret the Lambda returns and properly handles the embedded checkout lifecycle.
3. Multi-origin CORS fix for Lambda
During testing, browser preflight requests (OPTIONS) were being rejected. We patched the Lambda handler to allow the staging origin and derive the return URL from the request origin:
origin = event.get('headers', {}).get('origin', 'https://adamcherrycomics.dangerouscentaur.com')
return_url = f"{origin}/return"
This eliminates hardcoded URLs and makes the Lambda reusable across staging and production deployments.
4. CloudFront cache invalidation
After deploying the patched index.html` to S3, we invalidated the CloudFront distribution with path /* to bust stale cached versions.
aws cloudfront create-invalidation \
--distribution-id E2Q4UU71SRNTMB \
--paths "/*" \
--profile finalconstructclean
Testing & Validation
We ran three rounds of smoke tests using Playwright against staging before promoting to production:
- Round 1: Initial modal load and Stripe.js initialization check
- Round 2: Preflight CORS request validation with explicit API Gateway CORS headers
- Round 3: Full embedded checkout flow (form fill, payment submission, return URL navigation)
We also tailed CloudWatch logs during live invocations to confirm PaymentIntent creation and CORS header inclusion.
Cleanup & Outstanding Items
Completed:
- ✅ Checkout flow now functional (PaymentIntent + embedded modal)
- ✅ CORS headers correct for staging and production origins
- ✅ CloudFront cache fresh
- ✅ SEO audit: all pages return 200; no broken links in product cards
Outstanding / Recommended:
- Delete the unused empty bucket
s3://adamcherrycomics.dangerouscentaur.com/(leftover from initial setup) - Request Adam's confirmation that the checkout flow works end-to-end (last verified 2026-05-21)
- Consider acquiring the apex domain
adamcherrycomics.comto improve brand presence and SEO - Audit any remaining "DM to Buy" buttons in the source; ensure all product flows use Stripe checkout
- Document Stripe fee structure (2.9% + fixed fee per transaction