# Stripe PaymentIntent in a React SPA
In a Vite or Create React App SPA there is no server component: your backend (any framework) exposes an endpoint that creates the PaymentIntent and returns the client secret. The SPA renders Elements and confirms the card.
## Backend endpoint (any framework)
Create the intent with the secret key and return only the client secret. Example:
```
const intent = await stripe.paymentIntents.create({
amount: 2000,
currency: 'usd',
automatic_payment_methods: { enabled: true },
});
// respond with JSON carrying the client secret
```
## Frontend: Elements plus confirmCardPayment
```
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
const stripePromise = loadStripe('YOUR_PUBLISHABLE_KEY_VALUE');
function CheckoutForm({ clientSecret }) {
const stripe = useStripe();
const elements = useElements();
const pay = async (e) => {
e.preventDefault();
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: elements.getElement(CardElement) },
});
if (error) {
console.log(error.message);
} else if (paymentIntent.status === 'succeeded') {
console.log('paid');
}
};
return (
[form onSubmit={pay}]
[CardElement /]
[button disabled={!stripe}]Pay[/button]
[/form]
);
}
export default function App({ clientSecret }) {
return (
[Elements stripe={stripePromise}]
[CheckoutForm clientSecret={clientSecret} /]
[/Elements]
);
}
```
## Notes
- Fetch the client secret from your backend when the checkout page loads, then render Elements.
- `confirmCardPayment` handles 3D Secure automatically; check `paymentIntent.status` after it resolves.
- The publishable key is public by design. The secret key never appears in frontend code.