Stripe Payment Intents: Modern One-Time Payment Processing
In this tutorial, you will learn about Stripe Payment Intents: Modern One. We cover key concepts, practical examples, and best practices to help you master this topic.
Payment Intents track a payment from creation through authentication and capture, supporting Strong Customer Authentication (3D Secure), dynamic payment methods, and status management.
What You'll Learn
How to create and confirm PaymentIntents, handle 3D Secure authentication, manage payment statuses, save payment methods for reuse, and confirm with saved methods.
Why It Matters
Payment Intents are Stripe's modern payment API, required for SCA Compliance and supporting 135+ payment methods. DodaTech uses PaymentIntents for all one-time purchases with automatic 3D Secure handling.
Real-World Use
A customer buys a Pro plan. The server creates a PaymentIntent, the client confirms it with card details via Stripe Elements, 3D Secure triggers if needed, and the Webhook confirms success.
flowchart LR
A["Create\nPaymentIntent"] --> B["Requires\nPaymentMethod"]
B --> C["Client Confirms\n+ 3D Secure"]
C --> D["Processing"]
D --> E["Succeeded"]
D --> F["Requires\nAction"]
F --> G["Customer\nAuthenticates"]
G --> D
style A fill:#6772e5,color:#fff
style C fill:#dbeafe,stroke:#2563eb
style E fill:#bbf7d0,stroke:#16a34a
Creating a PaymentIntent
import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
def create_payment_intent(amount, currency="usd"):
intent = stripe.PaymentIntent.create(
amount=amount,
currency=currency,
automatic_payment_methods={"enabled": True},
description="Pro Plan Purchase",
metadata={"order_id": "ORD-12345"}
)
print(f"PaymentIntent: {intent.id}")
print(f"Amount: ${intent.amount/100:.2f}")
print(f"Status: {intent.status}")
print(f"Client Secret: {intent.client_secret[:20]}...")
return intent
intent = create_payment_intent(2999)
# Expected output:
# PaymentIntent: pi_3Mqwerty19QfG5XG0gTzFz1L
# Amount: $29.99
# Status: requires_payment_method
Handling 3D Secure
# Client confirms the PaymentIntent (JavaScript)
# import { confirmCardPayment } from "@stripe/stripe-js";
#
# const { error, paymentIntent } = await stripe.confirmCardPayment(
# clientSecret,
# { payment_method: { card: cardElement } }
# );
#
# if (error) {
# console.log("Payment failed:", error.message);
# } else if (paymentIntent.status === "requires_action") {
# // 3D Secure - cardholder must authenticate
# stripe.confirmCardPayment(clientSecret);
# } else if (paymentIntent.status === "succeeded") {
# console.log("Payment succeeded!");
# }
# Server-side check after confirmation
def check_payment_status(payment_intent_id):
intent = stripe.PaymentIntent.retrieve(payment_intent_id)
print(f"Status: {intent.status}")
if intent.status == "succeeded":
print("Payment completed successfully")
return True
elif intent.status == "requires_action":
print("3D Secure authentication required — client must complete")
return False
elif intent.status == "requires_payment_method":
print("Payment failed — retry with different method")
return False
return False
Saving Payment Methods
def create_payment_intent_with_setup(amount, customer_id):
intent = stripe.PaymentIntent.create(
amount=amount,
currency="usd",
customer=customer_id,
setup_future_usage="off_session",
automatic_payment_methods={"enabled": True}
)
print(f"PaymentIntent with save: {intent.id}")
return intent
# Later, charge off-session:
def charge_saved_method(customer_id, payment_method_id, amount):
intent = stripe.PaymentIntent.create(
amount=amount,
currency="usd",
customer=customer_id,
payment_method=payment_method_id,
off_session=True,
confirm=True
)
print(f"Off-session charge: {intent.id} ({intent.status})")
return intent
Common Mistakes
1. Not Handling requires_action Status
Many cards require 3D Secure. If you don't handle requires_action, the payment stays pending forever. Always check status and redirect for authentication.
2. Confirming on the Server
Payment Intents should be confirmed on the client side to handle SCA. Server-side confirmation without SCA handling fails for regulated cards.
3. Not Using Idempotency Keys
Network retries without idempotency create duplicate PaymentIntents. Use idempotency_key on all creation requests.
4. Forgetting automatic_payment_methods
Without automatic_payment_methods: { enabled: true }, only card payments work. Enable it to accept wallets, bank transfers, and local methods.
5. Ignoring PaymentIntent Status Transitions
A PaymentIntent can go through requires_payment_method -> requires_action -> processing -> succeeded. Handle each status appropriately.
Practice Questions
- What is the difference between PaymentIntent and Charge?
- How does 3D Secure work with Payment Intents?
- What does
setup_future_usagedo? - How do you handle failed Payment Intents?
Answers:
- PaymentIntent supports SCA, dynamic payment methods, status tracking, and saved payment methods. Charges are deprecated with limited features.
- PaymentIntent detects when SCA is needed and returns
requires_actionstatus withnext_action.redirect_to_urlfor authentication. setup_future_usageindicates the payment method should be saved for future off-session charges. Stripe saves it for reuse.- Check
intent.status === 'requires_payment_method', getintent.last_payment_errorfor the reason, let the client retry with a different method.
Challenge: Build a complete PaymentIntent flow: create PI on server with amount + metadata, confirm on client with Stripe Elements, handle 3D Secure if needed, verify success via webhook, and save the payment method for future charges.
FAQ
Mini Project
Build a one-time payment flow: server creates PaymentIntent with metadata, client confirms with card input, handle 3D Secure redirect, save payment method for future charges, verify success via webhook, and display confirmation page.
What's Next
Checkout Session — build hosted payment pages with Stripe Checkout.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro