Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
Build an in-app integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
    Overview
    Payment Element
      Payment Element best practices
      Card Element comparison
      Migrate to the Payment Element with Payment Intents
      Migrate to the Payment Element with Checkout Sessions
      Migrate to Confirmation Tokens
    Express Checkout Element
    Address Element
    Currency Selector Element
    Link Authentication Element
    Payment Method Messaging Element
In-app Elements
Payment scenarios
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Other Stripe products
Financial Connections
Crypto
Climate
HomePaymentsWeb ElementsPayment Element

Migrate to the Payment Element with the Checkout Sessions API

Accept many payment methods with a single Element, while also managing taxes, shipping, discounts, currency conversion, and more.

Copy page

Previously, each payment method (cards, iDEAL, and so on) required a separate Element. By migrating to the Payment Element, you can accept many payment methods with a single Element. You can use additional capabilities by migrating to Checkout Sessions from Payment Intents, which enables your integration to manage subscriptions, discounts, shipping, and currency conversion.

If you’re using the Card Element with PaymentIntents or SetupIntents, and only want to migrate to the Payment Element, see migrate to the Payment Element instead. You can also compare other payment integrations if neither fit your use case.

PaymentIntents and SetupIntents each have their own set of migration guidelines. See the appropriate guide for your integration path, including example code.

If your existing integration uses the Payment Intents API to create and track payments or save card details during a payment, follow the steps below to use the Payment Element with Checkout Sessions.

Enable payment methods

Caution

This integration path doesn’t support BLIK or pre-authorized debits that use the Automated Clearing Settlement System (ACSS).

View your payment methods settings and enable the payment methods you want to support. You need at least one payment method enabled to create a Checkout Session.

By default, Stripe enables cards and other common payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See Payment method support for product and payment method support, and our pricing page for fees.

Migrate your PaymentIntent creation call
Server-side

Set the SDK to use at least the 2025-03-31.basil API version.

TypeScript
// Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys import Stripe from 'stripe'; const stripe = new Stripe(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
, { apiVersion: '2025-03-31.basil;' as any, });

Because the Payment Element allows you to accept multiple payment methods, we recommend using dynamic payment methods, which are automatically enabled if you don’t pass payment_method_types into the Checkout Session. When enabled, Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of payment methods available for your customers. We prioritize payment methods that increase conversion and are most relevant to the currency and location of the customer.

Update your PaymentIntent creation call to create a Checkout Session instead. In the Checkout Sessions instance, you’ll pass:

  • line_items: Represents what’s in the order
  • ui_mode: custom: Indicates that you’re using embedded components
  • mode: payment: Indicates that you’ll accept one-time payments for the Checkout Session
  • return_url: Represents the URL to redirect your customer back to after they authenticate or cancel their payment on the payment method’s app or site.

In addition, return the Checkout Session’s client_secret to the client-side to use later.

Each Checkout Session generates a PaymentIntent upon confirmation. If you want to retain any extra parameters from your current integration while creating a PaymentIntent, refer to the options available in payment_intent_data.

Before
After
Ruby
intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', payment_method_types: ['card'], })
Ruby
session = Stripe::Checkout::Session.create({ line_items: [ { price_data: { currency: 'usd', product_data: {name: 'T-shirt'}, unit_amount: 1099, }, quantity: 1, }, ], mode: 'payment', ui_mode: 'custom', return_url: '{{RETURN_URL}}', }) { clientSecret: session.client_secret, }.to_json

OptionalAdditional Checkout Session options
Server-side

Migrate your Elements instance
Client-side

Include the Stripe.js script on your checkout page by adding it to the head of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.

To update Stripe.js to basil from v3, include the following script tag: <script src="https://js.stripe.com/basil/stripe.js"></script>. To learn more about Stripe.js versioning, see the Stripe.js versioning and support policy.

checkout.html
<head> <title>Checkout</title> <script src="https://js.stripe.com/basil/stripe.js"></script> </head>

Create a fetchClientSecret function that retrieves the client secret from your server and returns a promise that resolves with the client secret.

Replace your stripe.elements() call with stripe.initCheckout, passing in fetchClientSecret. initCheckout returns a promise that resolves to a Checkout instance.

The Checkout object acts as the foundation of your checkout page, because it contains data from the Checkout Session and methods to update the Session.

Use the object returned by checkout.session() as your reference for prices. We recommend reading and displaying the total and lineItems from the session in your UI.

This lets you turn on new features with minimal code changes. For example, adding manual currency prices requires no UI changes if you display the total.

Before
After
const stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); const elements = stripe.elements();
checkout.js
const stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); const fetchClientSecret = () => { return fetch('/create-checkout-session', {method: 'POST'}) .then((response) => response.json()) .then((json) => json.checkoutSessionClientSecret); }; stripe.initCheckout({fetchClientSecret}) .then((checkout) => { const checkoutContainer = document.getElementById('checkout-container'); checkoutContainer.append(JSON.stringify(checkout.lineItems, null, 2)); checkoutContainer.append(document.createElement('br')); checkoutContainer.append(`Total: ${checkout.session().total.total.amount}`); });
index.html
<div id="checkout-container"></div>

Collect customer email
Client-side

Migrating to embedded components requires the additional step of collecting your customer’s email.

If you already pass in an existing customer_email or Customer with a valid email set when creating the Checkout Session, you can skip this step.

If you implement your own email validation, you can pass in the validated email on checkout.confirm and skip this step.

Create an email input to collect your customer’s email address. Call updateEmail when your customer finishes the input to validate and save the email address.

Depending on the design of your checkout form, you can call updateEmail in the following ways:

  • Directly before submitting the payment. You can also call updateEmail to validate earlier, such as on input blur.
  • Before transitioning to the next step, such as clicking a Save button, if your form includes multiple steps.
index.html
<input type="text" id="email" /> <div id="email-errors"></div>
checkout.js
stripe.initCheckout({fetchClientSecret}).then((checkout) => { const emailInput = document.getElementById('email'); const emailErrors = document.getElementById('email-errors'); emailInput.addEventListener('input', () => { // Clear any validation errors emailErrors.textContent = ''; }); emailInput.addEventListener('blur', () => { const newEmail = emailInput.value; checkout.updateEmail(newEmail).then((result) => { if (result.error) { emailErrors.textContent = result.error.message; } }); }); });

Add the Payment Element
Client-side

You can now replace the Card Element and individual payment method Elements with the Payment Element. The Payment Element automatically adjusts to collect input fields based on the payment method and country (for example, full billing address collection for SEPA Direct Debit) so you don’t have to maintain customized input fields anymore.

The following example replaces CardElement with PaymentElement:

checkout.html
<form id="payment-form"> <div id="card-element"> </div> <div id="payment-element"> <!-- Mount the Payment Element here --> </div> <button id="submit">Submit</button> </form>
checkout.js
const cardElement = elements.create("card"); cardElement.mount("#card-element"); const paymentElement = checkout.createPaymentElement(); paymentElement.mount("#payment-element");

Update the submit handler
Client-side

Instead of using individual confirm methods like stripe.confirmCardPayment or stripe.confirmP24Payment, use checkout.confirm to collect payment information and submit it to Stripe.

To confirm the Checkout Session, update your submit handler to use checkout.confirm instead of individual confirm methods.

When called, checkout.confirm attempts to complete any required actions, such as displaying a 3DS dialog or redirecting them to a bank authorization page. When confirmation is complete, customers redirect to the return_url you configured, which normally corresponds to a page on your website that provides the status of the payment.

If you want to keep the same checkout flow for card payments and only redirect for redirect-based payment methods, you can set redirect to if_required.

The following code example replaces stripe.confirmCardPayment with checkout.confirm:

Before
After
// Create the PaymentIntent and obtain clientSecret const res = await fetch("/create-intent", { method: "POST", headers: {"Content-Type": "application/json"}, }); const {client_secret: clientSecret} = await res.json(); const handleSubmit = async (event) => { event.preventDefault(); if (!stripe) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setLoading(true); const {error} = await stripe.confirmCardPayment(clientSecret, { payment_method: { card: elements.getElement(CardElement) } }); if (error) { handleError(error); } };
const handleSubmit = async (event) => { event.preventDefault(); if (!stripe) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setLoading(true); const {error} = await checkout.confirm(); if (error) { handleError(error); } };

OptionalSave payment details during a payment

Handle post-payment events
Server-side

Stripe sends a checkout.session.completed event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept different types of payment methods with a single integration.

In addition to handling the checkout.session.completed event, we recommend handling these other events when collecting payments with the Payment Element:

EventDescriptionAction
checkout.session.completedSent when a customer successfully completes a payment.Send the customer an order confirmation and fulfill their order.
checkout_session.async_payment_succeededSent when payment by a customer using a delayed payment method finally succeeds.Send the customer an order confirmation and fulfill their order.
checkout.session.async_payment_failedSent when a customer attempts a payment, but the payment fails.If a payment transitions from async_payment_failed, offer the customer another attempt to pay.
checkout.session.expiredSent when a customer’s checkout session has expired, which is after 24 hours.If a payment transitions from expired to payment_failed, offer the customer an attempt to reload the checkout page and create a new checkout session.

Test the integration

  1. Navigate to your checkout page.
  2. Fill out the payment details with a payment method from the following table. For card payments:
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Submit the payment to Stripe.
  4. Go to the Dashboard and look for the payment on the Transactions page. If your payment succeeded, you’ll see it in that list.
  5. Click your payment to see more details, like billing information and the list of purchased items. You can use this information to fulfill the order.
Card numberScenarioHow to test
The card payment succeeds and doesn’t require authentication.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The card payment requires authentication.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The card is declined with a decline code like insufficient_funds.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The UnionPay card has a variable length of 13-19 digits.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

See Testing for additional information to test your integration.

See also

  • Collect Billing and shipping addresses
  • Use promotion codes for one-time payments
  • Collect tax automatically
  • Enable adjustable line item quantity
  • Automatic currency conversion
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc