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.
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 one-time 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 pre-authorized debits that use the Automated Clearing Settlement System (the related setting). Also, if you create the deferred intent from the client-side, you can’t use customer_balance with dynamic payment methods because the PaymentIntent requires a customer-configured Account or Customer object, which the client-side flow doesn’t support. To use customer_balance, create the PaymentIntent server-side with an Account or Customer and return its client_secret to the client.
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
Upgrade your SDK to use the latest API version.
Command Line
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Available as a gem
sudo gem install stripe
Gemfile
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
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: elements : Indicates that you’re using Elements
- 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
Select a language
Ruby
Python
PHP
Node.js
Go
Java
.NET
No results
require 'stripe'
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')
intent = client.v1.payment_intents.create({
amount: 1099,
currency: 'usd',
payment_method_types: ['card'],
})
Select a language
Ruby
Python
PHP
Node.js
Go
Java
.NET
No results
require 'stripe'
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')
session = client.v1.checkout.sessions.create({
line_items: [
{
price_data: {
currency: 'usd',
product_data: {name: 'T-shirt'},
unit_amount: 1099,
},
quantity: 1,
},
],
mode: 'payment',
ui_mode: 'elements',
return_url: '{{RETURN_URL}}',
})
{
clientSecret: session.client_secret,
}.to_json
Optional Additional 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.
Ensure you’re on the latest Stripe.js version by including the following script tag <script src=“https://js.stripe.com/dahlia/stripe.js”></script>. Learn more about Stripe.js versioning and support policy.
checkout.html
Create clientSecret as a Promise<string> | string that contains the client secret returned by your server.
Replace your stripe.elements() call with stripe.initCheckoutElementsSdk, passing in clientSecret. initCheckoutElementsSdk returns 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 actions.getSession() 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 clientSecret = fetch('/create-checkout-session', {method: 'POST'})
.then((response) => response.json())
.then((json) => json.checkoutSessionClientSecret);
const checkout = stripe.initCheckoutElementsSdk({clientSecret});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const session = loadActionsResult.actions.getSession();
const checkoutContainer = document.getElementById('checkout-container');
checkoutContainer.append(JSON.stringify(session.lineItems, null, 2));
checkoutContainer.append(document.createElement('br'));
checkoutContainer.append(`Total: ${session.total.total.amount}`);
}
index.html
<div id="checkout-container"></div>
Collect customer email Client-side
Migrating to Elements requires the additional step of collecting your customer’s email.
You must provide a valid customer email when completing a Checkout Session.
Use the Contact Details Element to collect your customer’s email address. It handles email collection and validation for you, and helps customers sign in to Link.
Alternatively, you can:
- Pass in customer_email , customer_account (for customers represented as customer-configured Account objects), or customer (for customers represented as Customer objects) when creating the Checkout Session. Stripe validates emails provided this way.
- These prefill the session with an email that customers can’t edit on the checkout page. If you want to prefill with an editable email, use defaultValues.email when initializing Checkout.
- Pass in an email you already validated on updateEmail or checkout.confirm .
checkout.html
<div id="contact-details-element">
<!--Stripe.js injects the Contact Details Element-->
</div>
checkout.js
const contactDetailsElement = checkout.createContactDetailsElement();
contactDetailsElement.mount("#contact-details-element");
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 the related setting 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 actions.confirm to collect payment information and submit it to Stripe.
To confirm the Checkout Session, update your submit handler to use actions.confirm instead of individual confirm methods.
When called, actions.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 actions.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 actions.confirm();
if (error) {
handleError(error);
}
};
Optional Save 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:
| Event | Description | Action |
|---|---|---|
| checkout.session.completed | Sent when a customer successfully completes a payment. | Send the customer an order confirmation and fulfill their order. |
| checkout_session.async_payment_succeeded | Sent 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_failed | Sent 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.expired | Sent 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
- Navigate to your checkout page.
- 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.
- Submit the payment to Stripe.
- Go to the Dashboard and look for the payment on the Transactions page . If your payment succeeded, you’ll see it in that list.
- 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 number | Scenario | How 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.
