Your live URL accepts the test card and rejects every real card because the site is still in stripe test mode.
Checkout looks finished. The pay button loads. A customer enters their Visa and gets declined. You try 4242 4242 4242 4242 and it works. Stripe Dashboard shows the payment under Test data, not under Live payments. Your launch is live to humans but still wired to sandbox keys.
Your live site is still on test keys
Production hostname does not mean production Stripe keys. Many AI-built apps ship with pk_test_ and sk_test_ copied from a tutorial. The frontend renders Checkout. The backend creates PaymentIntents with a test secret. Stripe processes the flow in the test environment. Money never settles to your bank account.
Environment variables are the usual culprit. Preview builds in Lovable or Vercel may inject test keys automatically. When you publish, those same values ride along unless you overwrite them on the host. A .env file on your laptop with live keys does nothing for the deployed bundle.
Symptoms you will see on a real domain with test keys:
- Only
4242 4242 4242 4242or other test numbers succeed - Real cards show generic decline or authentication errors
- Stripe Dashboard β Payments lists the charge only when Test mode toggle is on
- Customers receive test receipts, not live receipts
- Your payout balance stays at zero
Webhooks make this worse. A handler registered in Test mode does not fire for live charges, and vice versa. If you fixed webhooks only in test, live payments still leave your database empty. See stripe webhook for wiring the live endpoint after you swap keys.
Only the test card works on checkout
Stripe publishes test card numbers for development. 4242 4242 4242 4242 is the famous one. They always succeed in test mode. They never work in live mode. Real PANs always fail in test mode.
Founders often discover this during a soft launch. A friend tries to pay. It fails. You assume Stripe is broken, rerun the AI prompt, and burn an afternoon on UI tweaks that cannot fix a key prefix.
Quick check without reading code:
- Open your live checkout page in the browser.
- Open DevTools β Network.
- Start a payment and look for requests to Stripe.
- If the publishable key in the payload starts with
pk_test_, you are in test mode on a public URL.
Some teams embed the key in client-side JavaScript the model generated. Others load it from an env var that was never updated on the host. Both paths lead to the same symptom: magic test card yes, real wallet no.
Why the AI left test keys in production
Coding assistants default to safe sandbox values so you do not accidentally charge cards while iterating. That is correct in development. They rarely add a launch checklist that says replace test keys before DNS goes public. The success page renders, so the tool reports the feature done.
Stripe’s own docs show test keys first. Copy-paste from ChatGPT or Lovable often stops at sk_test_... without a second step for live. Treat key rotation as part of go-live, not a separate ops task you can skip.
Switching to live mode without breaking checkout
Moving from test to live is not a single toggle in your repo. It is a coordinated swap of keys, webhooks, and Dashboard mode. Skip a step and checkout breaks in a new way.
- Complete Stripe account activation (business details, bank account) so Live mode is enabled.
- In Stripe Dashboard, turn off the Test mode switch (top right) to view Live data.
- Go to Developers β API keys and reveal the live publishable and secret keys (
pk_live_,sk_live_). - Open your hosting provider’s production environment settings (Vercel, Netlify, Railway, Lovable publish env, etc.).
- Replace every
STRIPE_*variable that contains_test_with the live values. Include webhook signing secrets after you create a live endpoint. - Redeploy or restart so the new env vars load. Cached builds keep old keys until redeploy.
- Register a live webhook URL under Live mode in the Dashboard. Point it at your production HTTPS path.
- Run one small real charge with your own card. Confirm it appears under Live payments and your app grants access.
Keep test keys in local .env.local for development. Never commit live secrets to git. Production and staging should each have their own live or test set, clearly labeled.
Other production gaps often hide next to Stripe keys. Auth URLs, Supabase policies, and missing env vars surface on the same deploy. The hub article on lovable deploy lists the settings editors skip when publish feels done.
Stripe test mode checklist before you call it launch
Walk through this list on the URL customers actually use, not localhost.
- Production env vars use
pk_live_andsk_live_, not_test_ - Checkout with a real card succeeds and appears in Live mode Dashboard
- Test card
4242...fails on production (proves you left test mode) - Live webhook endpoint returns 200 and updates your database
- Payout settings and business profile are complete
- Staging still uses test keys; production uses live keys β never mixed on one host
Test mode is for building. Live mode is for money. A custom domain on test keys is still a demo, no matter how polished the UI looks.
FAQ
How do I know if my site is still in stripe test mode?
Check your production environment variables. If STRIPE_SECRET_KEY or NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY starts with sk_test_ or pk_test_, you are in test mode. Real cards will decline or fail while 4242 4242 4242 4242 still works.
Can I use test keys on a live domain during beta?
You can, but no real money moves and no real card will succeed. Stripe treats test and live as separate accounts. Customers who try their own card see a failure even when checkout looks polished.
What is the fastest way to switch from stripe test mode to live?
Create live API keys in the Stripe Dashboard, replace every test key in production env vars, register live webhooks, and run one small real charge. Test keys in .env.local do not affect production until you copy live keys to the host.