Diagnosing and Fixing Stripe Embedded Checkout on adamcherrycomics.dangerouscentaur.com
Executive Summary
During a recent handoff review of the Adam Cherry Comics storefront, we discovered a critical mismatch between documented checkout behavior and production reality. The Lambda function was already configured for Stripe's embedded checkout (`ui_mode="embedded"`), but the frontend modal was missing the required Stripe.js library initialization, causing checkout to fail silently in production. This post documents the diagnostic process, the fixes applied, and the architectural decisions that ensure compliance with our platform's modal-first checkout standard.
What Was Done
We performed a multi-layer verification of the adamcherrycomics checkout flow across production, staging, and EC2 development environments:
- Verified page health: All 12 product pages returned HTTP 200 from CloudFront distribution
E2Q4UU71SRNTMB - Diagnosed checkout failure: Lambda
adam-cherry-checkoutwas correctly minting Stripe PaymentIntent objects withui_mode="embedded", but the frontend modal JS had no Stripe.js initialization - Patched frontend: Added missing
<script src="https://js.stripe.com/v3/"></script>tag and modal initialization logic toindex.html - Fixed Lambda CORS: Updated checkout Lambda to derive
RETURN_URLfrom the request'sOriginheader, supporting both staging and production domains - Validated multi-origin support: Patched API Gateway
n0nh1zscq4CORS configuration to acceptadamcherrycomics.dangerouscentaur.comand staging origins - Smoke-tested: Deployed fixes to staging, validated with Playwright, promoted to production, and re-verified
Technical Details: The Diagnostic Journey
Layer 1: Handoff vs. Reality
The handoff documentation claimed the site had been migrated from embedded modal to "hosted redirect" due to `stripe-python 15.1.0` rejecting the embedded mode. However, inspecting the live Lambda code revealed it was already using `ui_mode="embedded"` when creating PaymentIntent objects. The actual problem wasn't the backend—it was the frontend.
We captured the live S3 object from s3://dc-sites/adamcherrycomics.dangerouscentaur.com/index.html and compared it against the EC2 development copy. The missing piece: no Stripe.js library load and no modal initialization in the checkout button handler.
Layer 2: Lambda Configuration Audit
The checkout Lambda at adam-cherry-checkout was correctly configured:
# Lambda handler (simplified)
def lambda_handler(event, context):
publishable_key = os.environ['STRIPE_PUBLISHABLE_KEY']
secret_key = os.environ['STRIPE_SECRET_KEY']
intent = stripe.PaymentIntent.create(
amount=int(float(product_price) * 100),
currency='usd',
ui_mode='embedded',
return_url=derive_return_url(event) # ORIGIN-aware
)
return {
'statusCode': 200,
'body': json.dumps({
'client_secret': intent.client_secret
})
}
The Lambda was returning a valid PaymentIntent client_secret, but the frontend had no code to receive it or mount the Stripe Elements form.
Layer 3: Frontend Modal Gap
Inspecting the live modal HTML (extracted from CloudFront), we found:
<!-- MISSING -->
<script src="https://js.stripe.com/v3/"></script>
<!-- BUTTON HANDLER -->
document.getElementById('checkout-btn').addEventListener('click', async () => {
// Called Lambda to get client_secret
// BUT: no Stripe object, no Elements initialization, no form mount
});
The JavaScript was making the API call to Lambda but couldn't do anything with the response because the Stripe library was never loaded.
Infrastructure & Deployment Changes
S3 & CloudFront
Files modified and deployed to s3://dc-sites/adamcherrycomics.dangerouscentaur.com/:
- index.html — Added Stripe.js library tag in
<head>and embedded checkout initialization in modal handler - CloudFront invalidation — Pattern
/index.htmlon distributionE2Q4UU71SRNTMBto clear cached versions
Lambda Environment & CORS
Updated adam-cherry-checkout Lambda:
- RETURN_URL derivation: Changed from hardcoded production URL to extracting from
event['headers']['Origin'], enabling the same Lambda to serve staging and production - CORS headers: Added
Access-Control-Allow-Origin: *(permissive for now; restrict to known origins in production hardening)
Patched API Gateway n0nh1zscq4 CORS configuration:
- Added staging origin (e.g.,
https://adamcherrycomics-staging.dangerouscentaur.com) - Ensured
OPTIONSpreflight requests route correctly before hitting the Lambda - Verified response headers include
Access-Control-Allow-Credentials: truefor session handling
Monitoring & Validation
During testing, we:
- Tailed Lambda CloudWatch logs:
/aws/lambda/adam-cherry-checkout - Ran direct Lambda invocations with test payloads to isolate backend errors from frontend issues
- Executed Playwright smoke tests against staging before promoting to production
- Verified each product page (12 total, prices $10–$40) could reach the modal and receive a valid PaymentIntent
Key Decisions & Rationale
Why Embedded Modal (Not Redirect)
We maintain a platform-wide rule: all checkout flows use embedded modal. This ensures:
- User experience: Customers stay on the merchant site instead of being redirected away
- Brand consistency: All partner sites follow the same UX pattern
- Conversion: Embedded flows typically outperform redirects in A/B studies
The handoff's claim that `stripe-python 15.1.0` couldn't support embedded mode was incorrect or outdated. The current Lambda code proves embedded mode works fine with the library version in use.