# Stripe PaymentIntent on Next.js App Router

Never create a PaymentIntent in a client component: the secret key would ship to the browser. The route handler creates the intent with the secret key and returns only the client secret; the client component confirms the payment with Elements.

## Server: route handler

```
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_API_KEY_VALUE);

export async function POST(request: Request) {
  const { amount, currency } = await request.json();
  // validate amount and currency against your own pricing here,
  // never trust them blindly from the client
  const intent = await stripe.paymentIntents.create({
    amount,
    currency,
    automatic_payment_methods: { enabled: true },
  });
  return Response.json({ 'clientSecret': intent.client_secret });
}
```

## Client: Elements plus confirm

```
'use client';
import { useEffect, useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_VALUE);

function Form() {
  const stripe = useStripe();
  const elements = useElements();
  const pay = async () => {
    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: 'https://example.com/order/complete' },
    });
    if (error) alert(error.message);
  };
  return (
    [fragment]
      [PaymentElement /]
      [button onClick={pay} disabled={!stripe}]Pay[/button]
    [/fragment]
  );
}

export default function Page() {
  const [secret, setSecret] = useState(null);
  useEffect(() => {
    fetch('/api/payment-intent', {
      method: 'POST',
      body: JSON.stringify({ amount: 2000, currency: 'usd' }),
    }).then((r) => r.json()).then((d) => setSecret(d.clientSecret));
  }, []);
  if (!secret) return [p]loading[/p];
  return (
    [Elements stripe={stripePromise} options={{ clientSecret: secret }}]
      [Form /]
    [/Elements]
  );
}
```

## Notes

- Amounts are in the smallest currency unit (cents for USD). Compute them server-side from your catalog, not from client input.
- `automatic_payment_methods` lets Stripe choose the right method per customer without you enumerating them.
- Handle the `return_url`: after redirect-based methods (3D Secure, iDEAL) Stripe sends the customer back there.