Use this when you build the payment form yourself with Stripe Elements and never send the customer to a Stripe-hosted page.
Your server creates the PaymentIntent, so the ids go in as metadata there —
the same place and the same keys as a
Checkout Session, on a different
object.
// app/api/payment-intent/route.js
import Stripe from "stripe";
import { cookies } from "next/headers";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST() {
// `cookies()` is only readable on the server, which is why the intent is
// created here and not in the browser.
//
// It is asynchronous from Next.js 15 on. Version 14 returns it directly,
// so drop the `await` there.
const cookieStore = await cookies();
const ts_vid = cookieStore.get("ts_vid")?.value;
const ts_vs = cookieStore.get("ts_vs")?.value;
// Left out entirely when absent: Stripe rejects a metadata value of
// `undefined`, and a visitor with cookies blocked simply has none.
const metadata = {};
if (ts_vid) metadata.ts_vid = ts_vid;
if (ts_vs) metadata.ts_vs = ts_vs;
const intent = await stripe.paymentIntents.create({
amount: 4900,
currency: "usd",
automatic_payment_methods: { enabled: true },
metadata,
});
return Response.json({ clientSecret: intent.client_secret });
}Subscriptions need it in a second place
A PaymentIntent covers one payment. If you are creating a subscription, put the same metadata on the subscription too — otherwise the first charge is attributed and every renewal after it is not.
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: "price_123" }],
// The first invoice's PaymentIntent gets this through `payment_settings`,
// but the SUBSCRIPTION needs its own copy: Stripe does not copy metadata
// between related objects, and renewals are billed from the subscription.
metadata,
});Stripe never copies metadata between objects. A PaymentIntent, a Checkout Session, a Subscription and an Invoice each carry their own. This is the single most common reason a custom integration attributes the first payment and nothing after it.
Why this is as good as Checkout
The ids are read on your server from a cookie the browser cannot forge, and recorded at the moment of purchase. Nothing depends on the customer reaching a particular page afterwards.
The only extra work compared with a Checkout Session is remembering the subscription's own metadata, above.