3) Pass the correct price to your checkout call

Where you render the subscribe buttons, feed the Price ID you want:

// example choose plan
<SubscribeButton priceId={import.meta.env.VITE_SUBS_PRICE_ID_MONTHLY || process.env.SUBS_PRICE_ID_MONTHLY} />
<SubscribeButton priceId={import.meta.env.VITE_SUBS_PRICE_ID_YEARLY || process.env.SUBS_PRICE_ID_YEARLY} />


(Adjust for your env system; some builds use VITE_, Next uses NEXT_PUBLIC_, etc.)

Your server route already expects priceId in body:

// POST /api/stripe/checkout
// body: { priceId, mode:'subscription', successUrl?, cancelUrl?, trialDays? }

4) (Optional) Force server-side mapping (harder to break)

If you don’t want the client to send the raw ID, accept a plan key and map server-side:

// server
const PRICE_MAP = {
  monthly: process.env.SUBS_PRICE_ID_MONTHLY!,
  yearly: process.env.SUBS_PRICE_ID_YEARLY!,
};

router.post('/api/stripe/checkout', requireAuth, async (req,res) => {
  const { plan = 'monthly' } = req.body;
  const priceId = PRICE_MAP[plan];
  if (!priceId) return res.status(400).json({ error: 'Unknown plan' });
  // ... create session with priceId ...
});


Client then calls:

apiRequest('POST','/api/stripe/checkout',{ plan: 'monthly' })

5) Verify it’s good

Dashboard → Developers → Logs
Try your button. You should see Create Checkout Session succeed.

Use test card 4242 4242 4242 4242 (any future exp, any CVC/ZIP) in Test mode.

After payment, ensure your webhook upgrades the user to pro. (If you haven’t added the webhook yet, I can drop that in—quick.)

6) Common pitfalls that cause “No such price”

Copying a live Price ID into test env or vice versa.

Using a Product ID (prod_...) instead of a Price ID (price_...).

Price was archived in Stripe (archived prices can’t be used).

You’re on the wrong Stripe account (e.g., personal vs company).

The server is using live sk_live while the client sent a test price_... (or the opposite).

7) Minimal code sanity

Client (robust parse; you already use something like this):

const createCheckout = async (priceId: string) => {
  const res = await apiRequest('POST','/api/stripe/checkout',{
    priceId,
    mode: 'subscription',
    successUrl: `${window.location.origin}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
    cancelUrl: `${window.location.origin}/billing/cancelled`,
    trialDays: 7,
  });
  const text = await res.text();
  const isJson = (res.headers.get('content-type')||'').includes('application/json');
  if (!res.ok) throw new Error(isJson ? (JSON.parse(text).error||'Checkout failed') : `HTTP ${res.status}`);
  const data = JSON.parse(text);
  return data as { id: string; url: string };
};


Server:

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: priceId, quantity: 1 }],
  allow_promotion_codes: true,
  customer_email: req.user.email,
  subscription_data: trialDays ? { trial_period_days: trialDays, metadata: { uid: req.user.uid } } : { metadata: { uid: req.user.uid } },
  success_url: successUrl ?? `${process.env.PUBLIC_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: cancelUrl ?? `${process.env.PUBLIC_URL}/billing/cancelled`,
});
