Understanding Payment Intents
A PaymentIntent is a core object in Stripe's API that represents your intent to collect a payment from a customer. Think of it as a transaction wrapper that tracks the entire payment lifecycle--from initial creation through confirmation, authentication, and eventual success or failure. The PaymentIntent API is designed to handle the complexity of modern payments, including scenarios where additional authentication is required or where the payment method requires multiple steps to complete.
The PaymentIntent object contains all the information about a potential transaction, including the amount, currency, payment method types, and current status. As the payment progresses through its lifecycle, Stripe updates the PaymentIntent's status, allowing your application to respond appropriately to each stage. This status-driven approach means you don't need to track complex state machines yourself--you simply listen for status changes and respond accordingly.
By using Stripe's Payment Intents API, you can build payment flows that automatically adapt to different payment methods and authentication requirements without maintaining separate code paths for each scenario. This approach handles the complexity of modern payments while providing a consistent experience for your customers across different regions and payment types. For a comprehensive overview of the complete payment lifecycle, including all status transitions and error handling scenarios, see our guide on Stripe Payment Intents.
The PaymentIntent Lifecycle
The lifecycle of a PaymentIntent follows a predictable path that you need to understand to build robust payment flows. When you create a PaymentIntent, it begins in a requires_payment_method status, indicating that you're waiting for the customer to provide payment details. The payment method can be attached and confirmed in various ways depending on your integration approach--whether you're using Stripe Elements, the mobile SDKs, or custom forms.
After payment details are submitted, the PaymentIntent may transition through several intermediate states. If additional authentication is needed, such as with 3D Secure for cards, the status becomes requires_action. Your application must handle this state by presenting the authentication challenge to the customer. Once authentication completes successfully, the PaymentIntent returns to processing before finally reaching succeeded when the payment is complete.
Failed payments result in a canceled status, but Stripe also provides detailed error codes that explain why the failure occurred. Common reasons include insufficient funds, expired cards, or cards that have been flagged for fraud. Your integration should handle these error cases gracefully, providing helpful feedback to customers so they can resolve issues and retry their payment. Understanding these transitions is crucial for building payment flows that provide clear feedback to customers at every step.
1import Stripe from 'stripe';2 3// Initialize with your secret key from environment variables4const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {5 apiVersion: '2025-04-30.basil',6 typescript: true,7});1async function createPaymentIntent(amount: number, currency: string = 'usd') {2 const paymentIntent = await stripe.paymentIntents.create({3 amount: amount, // Amount in cents (e.g., $10.00 = 1000)4 currency: currency,5 automatic_payment_methods: {6 enabled: true, // Allows Stripe to automatically choose available payment methods7 },8 metadata: {9 order_id: '6735-ORDER-123', // Your internal order identifier10 customer_email: '[email protected]', // For receipts and communication11 },12 });13 14 return paymentIntent;15}1async function createAdvancedPaymentIntent(request: {2 amount: number;3 currency: string;4 customerId?: string;5 description?: string;6 receiptEmail?: string;7 returnUrl: string;8}) {9 const paymentIntent = await stripe.paymentIntents.create({10 amount: request.amount,11 currency: request.currency,12 automatic_payment_methods: { enabled: true },13 confirm: false, // Create in pending state for later confirmation14 customer: request.customerId, // Associate with a Stripe customer15 description: request.description,16 receipt_email: request.receiptEmail,17 return_url: request.returnUrl,18 payment_method_types: ['card'],19 setup_future_usage: 'on_session', // Save card for future payments20 metadata: {21 product_type: 'digital',22 subscription_tier: 'premium',23 },24 });25 26 return paymentIntent;27}Stripe API Pricing
International Cards
Cross-border fee varies
- Additional cross-border fee
- Multiple currencies supported
- Real-time conversion rates
Currency Conversion
When accepting non-USD currencies
- Competitive rates
- Automatic conversion
- Transparent fees
Best Practices for Payment Creation
Idempotency and Error Handling
Production payment systems must handle failures gracefully and ensure that retrying a request doesn't create duplicate charges. Stripe provides idempotency keys to solve this problem--when you include a unique key with your request, Stripe ensures that repeated requests with the same key produce the same result. This is essential for handling network timeouts, server errors, and any situation where you're unsure whether the original request succeeded.
When implementing idempotency, generate unique keys for each logical operation. A common approach is to use UUIDs or hash-based identifiers derived from the request parameters. Store these keys on the client side and include them with each request. If a request fails with a network error, you can safely retry using the same key without worrying about duplicate charges. Stripe's error responses will indicate whether the original request was actually processed or not.
Security and PCI Compliance
By using Stripe Elements or Stripe Checkout, you offload the burden of handling sensitive card data entirely. These pre-built UI components render iframes that securely collect and transmit card information directly to Stripe, ensuring that sensitive data never touches your servers. This significantly simplifies your compliance requirements and reduces security risk. For applications requiring additional identity verification during checkout, learn more about Stripe Identity.
When you create Payment Intents server-side and then confirm them using Stripe's client-side libraries, the card details are handled entirely within Stripe's secure infrastructure. Your application only ever interacts with tokens or PaymentIntents that represent the payment, never the raw card numbers. This approach meets PCI DSS Level 1 requirements with minimal effort on your part. For applications that must handle custom payment flows, Stripe provides tokens that represent payment methods without exposing raw card data. If you're building subscription-based services, consider integrating with Stripe Billing to manage recurring payments and customer lifecycles seamlessly.
Advanced Integration Patterns
For marketplace and platform integrations requiring payment splitting between multiple parties, Stripe Connect provides the necessary infrastructure. When creating payments in a connected account context, you specify the application fee you want to retain and the destination charges for your connected users. Our web development services can help you implement these advanced patterns along with robust error handling and monitoring.
1async function createIdempotentPaymentIntent(2 amount: number,3 idempotencyKey: string4) {5 try {6 const paymentIntent = await stripe.paymentIntents.create(7 {8 amount: amount,9 currency: 'usd',10 automatic_payment_methods: { enabled: true },11 },12 {13 idempotencyKey: idempotencyKey, // Prevents duplicate charges14 }15 );16 return { success: true, paymentIntent };17 } catch (error: unknown) {18 if (error instanceof Stripe.errors.StripeError) {19 if (error.type === 'IdempotencyError') {20 return { success: false, requiresRetry: false };21 }22 return { success: false, requiresRetry: error.type === 'RateLimitError' };23 }24 throw error;25 }26}1async function createConnectedAccountPayment(2 amount: number,3 connectedAccountId: string,4 applicationFee: number5) {6 const paymentIntent = await stripe.paymentIntents.create({7 amount: amount,8 currency: 'usd',9 automatic_payment_methods: { enabled: true },10 application_fee_amount: applicationFee, // Your platform's fee11 transfer_data: {12 destination: connectedAccountId, // Connected account to pay13 },14 });15 16 return paymentIntent;17}Use Payment Intents API
The modern approach for all new integrations, handling authentication and complex payment flows automatically
Implement Idempotency
Prevent duplicate charges by using idempotency keys for all payment creation requests
Leverage Stripe Elements
Offload PCI compliance and secure card handling with pre-built UI components
Handle Lifecycle States
Respond to status transitions like requires_action and processing for a smooth customer experience
Sources
- Stripe API Reference - Create a PaymentIntent - Official API parameters and response structure
- Stripe Pricing & Fees - Processing fees: 2.9% + 30¢ per successful card transaction
- Payment Intents API Overview - Complete lifecycle and best practices