Skip to content

The Payment Intents API Fresh

Use the Payment Intents API to build integrations that handle complex payment flows with a status that changes over the PaymentIntent's lifecycle. It tracks a payment from creation through checkout and triggers additional authentication steps when required.

Why use Payment Intents?

  • Automatic authentication handling (3D Secure, etc.)
  • No double charges
  • No idempotency key issues
  • Support for Strong Customer Authentication (SCA) and similar regulatory requirements
  • Works with Setup Intents and Payment Methods APIs for dynamic payment flows

Creating a PaymentIntent

bash
curl https://api.stripe.com/v1/payment_intents \
  -u "sk_test_YOUR_KEY:" \
  -d amount=1099 \
  -d currency=usd
javascript
const paymentIntent = await stripe.paymentIntents.create({
  amount: 1099,
  currency: 'usd',
  // Optionally specify payment methods
  payment_method_types: ['card'],
});

The API returns a PaymentIntent object with a client_secret — a unique key used on the client to confirm and complete the payment.

Best practices

  • Create early — Create a PaymentIntent as soon as you know the amount (e.g., when the customer begins checkout). This helps track the purchase funnel.
  • Update if amount changes — Call stripe.paymentIntents.update() if the cart total changes before confirmation.
  • Reuse interrupted sessions — Store the PaymentIntent ID in your session/cart. If checkout is interrupted, retrieve the existing PaymentIntent instead of creating a new one.
  • Use idempotency keys — Pass Idempotency-Key headers to prevent creating duplicate PaymentIntents for the same cart.

Passing the client secret to the client

The client_secret must be passed from your server to the client. Never expose your secret API key.

Single-page application (fetch)

javascript
// Server endpoint
app.get('/secret', async (req, res) => {
  const intent = await stripe.paymentIntents.create({
    amount: req.query.amount,
    currency: 'usd',
  });
  res.json({ client_secret: intent.client_secret });
});
javascript
// Client
const { client_secret } = await fetch('/secret').then(r => r.json());

Server-rendered pages

Render the client secret into your HTML template:

html
<script>
  const clientSecret = "{{ client_secret }}";
</script>

PaymentIntent lifecycle

StatusMeaning
requires_payment_methodNo payment method attached yet
requires_confirmationPayment method attached, waiting for confirmation
requires_actionCustomer must take action (e.g., 3D Secure authentication)
processingStripe is processing the payment
requires_captureAuth-only — awaiting capture
succeededPayment complete
canceledPaymentIntent was canceled

Confirming a PaymentIntent

On the client side, use Stripe.js to confirm:

javascript
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
  payment_method: {
    card: cardElement,
    billing_details: { name: 'John Doe' },
  },
});

if (error) {
  // Show error to customer
} else if (paymentIntent.status === 'succeeded') {
  // Payment succeeded
}

Retrieving a PaymentIntent

bash
curl https://api.stripe.com/v1/payment_intents/pi_xxx \
  -u "sk_test_YOUR_KEY:"

Updating a PaymentIntent

bash
curl https://api.stripe.com/v1/payment_intents/pi_xxx \
  -u "sk_test_YOUR_KEY:" \
  -X POST \
  -d amount=2000

Canceling a PaymentIntent

You can cancel a PaymentIntent before it's confirmed or while it has requires_action or requires_payment_method status:

bash
curl https://api.stripe.com/v1/payment_intents/pi_xxx/cancel \
  -u "sk_test_YOUR_KEY:" \
  -X POST
  • SetupIntent — Collect and save a payment method without charging it. Use for subscriptions or future off-session payments.
  • PaymentMethod — Stores payment details attached to a customer or PaymentIntent.
  • Charge — Created when a PaymentIntent is confirmed. Represents the actual attempt to move money.

Stripe API Reference - Self-contained docs reference, refreshed 2026-05-18