Build two-step confirmation
Add an optional review page or run validations after a user enters their payment details.
While we recommend the standard integration for most scenarios, this integration allows you to add an extra step in your checkout. This allows you to perform other actions before confirming the order, such as:
- Presenting the customer their order details for review.
- Running additional validations.
- Calculating tax and updating the total due.
Set up Stripe
First, you need a Stripe account. Register now.
Use our official libraries for access to the Stripe API from your application:
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'
Enable payment methods
Caution
This integration path doesn’t support the related setting or 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 PaymentIntent.
By default, Stripe enables cards and other prevalent 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.
Collect payment details Client-side
Use the Payment Element to securely send payment information collected in an iFrame to Stripe over an HTTPS connection.
Conflicting iFrames
Avoid placing the Payment Element within another iframe because it conflicts with payment methods that require redirecting to another page for payment confirmation.
Your checkout page URL must start with https:// rather than http:// for your integration to work. You can test your integration without using HTTPS, but remember to enable it when you’re ready to accept live payments.
Set up Stripe.js
The Payment Element is automatically available as a feature of Stripe.js. 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.
checkout.html
Create an instance of Stripe with the following JavaScript on your checkout page:
checkout.js
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
Add the Payment Element to your checkout page
The Payment Element needs a place to live on your checkout page. Create an empty DOM node (container) with a unique ID in your payment form:
checkout.html
<form id="payment-form">
<div id="payment-element">
<!-- Elements will create form elements here -->
</div>
<button id="submit">Submit</button>
<div id="error-message">
<!-- Display error message to your customers here -->
</div>
</form>
After your form loads, create an Elements instance with the mode, amount, and currency. These values determine which payment methods the Element presents to your customer.
Then, create an instance of the Payment Element and mount it to the container DOM node.
checkout.js
const options = {
mode: 'payment',
amount: 1099,
currency: 'usd',
// Fully customizable with appearance API.
appearance: {/*...*/},
};
// Set up Stripe.js and Elements to use in checkout form
const elements = stripe.elements(options);
// Create and mount the Payment Element
const paymentElementOptions = { layout: 'accordion'};
const paymentElement = elements.create('payment', paymentElementOptions);
paymentElement.mount('#payment-element');
The Payment Element renders a dynamic form that allows your customer to pick a payment method. The form automatically collects all necessary payments details for the payment method selected by the customer.
You can customize the Payment Element to match the design of your site by passing the appearance object into options when creating the Elements provider.
Collect addresses
By default, the Payment Element only collects the necessary billing address details. Some behavior, such as calculating tax or entering shipping details, requires your customer’s full address. You can:
- Use the Address Element to take advantage of autocomplete and localization features to collect your customer’s full address. This helps ensure the most accurate tax calculation.
- Collect address details using your own custom form.
Optional Customize the layout Client-side
Optional Customize the appearance Client-side
Optional Save and retrieve customer payment methods
Optional Additional Elements options Client-side
Create a ConfirmationToken Client-side
Use createPaymentMethod through a legacy implementation
If you’re using a legacy implementation, you might be using the information from stripe.createPaymentMethod to finalize payments on the server. Although we encourage you to follow this guide to Migrate to Confirmation Tokens, you can still access our old documentation to Build two-step confirmation.
When the customer submits your payment form, call stripe.createConfirmationToken to create a ConfirmationToken to send to your server for additional validation or business logic before confirmation. You can inspect the payment_method_preview field to run the additional logic.
checkout.js
const form = document.getElementById('payment-form');
const submitBtn = document.getElementById('submit');
const handleError = (error) => {
const messageContainer = document.querySelector('#error-message');
messageContainer.textContent = error.message;
submitBtn.disabled = false;
}
form.addEventListener('submit', async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
// Prevent multiple form submissions
if (submitBtn.disabled) {
return;
}
// Disable form submission while loading
submitBtn.disabled = true;
// Trigger form validation and wallet collection
const {error: submitError} = await elements.submit();
if (submitError) {
handleError(submitError);
return;
}
// Create the ConfirmationToken using the details collected by the Payment Element
const {error, confirmationToken} = await stripe.createConfirmationToken({
elements,
params: {
payment_method_data: {
billing_details: {
name: 'Jenny Rosen',
}
}
}
});
if (error) {
// This point is only reached if there's an immediate error when
// creating the ConfirmationToken. Show the error to your customer (for example, payment details incomplete)
handleError(error);
return;
}
// Now that you have a ConfirmationToken, you can use it in the following steps to render a confirmation page or run additional validations on the server
return fetchAndRenderSummary(confirmationToken)
});
Show the payment details on the confirmation page
At this point, you have all of the information you need to render the confirmation page. Call the server to obtain the necessary information and render the confirmation page accordingly.
app.js
Node.js
// Using Express
const express = require('express');
const app = express();
app.use(express.json());
app.post('/summarize-payment', async (req, res) => {
try {
// Retrieve the confirmationTokens and generate the response
const confirmationToken = await stripe.confirmationTokens.retrieve(req.body.confirmation_token_id);
const response = summarizePaymentDetails(confirmationToken);
// Send the response to the client
res.json(response);
} catch (e) {
// Display error on client
return res.json({ error: e.message });
}
});
function summarizePaymentDetails(confirmationToken) {
// Use confirmationToken.payment_method_preview to derive the applicable summary fields for your UI
return {
type: confirmationToken.payment_method_preview.type,
// Add other values (such as Tax Calculation amount) as needed here
};
}
confirmation.js
JavaScript
const fetchAndRenderSummary = async (confirmationToken) => {
const res = await fetch('/summarize-payment', {
method: "POST",
body: JSON.stringify({ confirmation_token_id: confirmationToken.id }),
});
const summary = await res.json();
// Render the summary object returned by your server
};
Optional Calculate Tax before checkout Server-side
Create a PaymentIntent Server-side
Run custom business logic immediately before payment confirmation
Navigate to step 5 in the finalize payments guide to run your custom business logic immediately before payment confirmation. Otherwise, follow the steps below for a simpler integration, which uses stripe.confirmPayment on the client to both confirm the payment and handle any next actions.
When the customer submits your payment form, create a PaymentIntent on your server with an amount and currency enabled.
Return the client secret value to your client for Stripe.js to use to complete the payment process.
The following example includes commented code to illustrate the optional Tax Calculation.
main.rb
Select a language
Ruby
Python
PHP
Node.js
Java
Go
.NET
No results
require 'stripe'
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
Stripe.api_key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
post '/create-intent' do
# If you used a Tax Calculation, optionally recalculate taxes
# confirmation_token = Stripe::ConfirmationToken.retrieve(params[:confirmation_token_id])
# summarized_payment_details = summarize_payment_details(confirmation_token)
intent = Stripe::PaymentIntent.create({
# To allow saving and retrieving payment methods, provide the Account ID.
customer_account: customer_account.id,
# If you used a Tax Calculation, use its `amount_total`.
# amount: summarized_payment_details.amount_total,
amount: 1099,
currency: 'usd',
# Specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.
automatic_payment_methods: {enabled: true},
# If you used a Tax Calculation, link it to the PaymentIntent to make sure any transitions accurately reflect the tax.
# hooks: {
# inputs: {
# tax: {
# calculation: tax_calculation.id
# }
# }
#}
},
#{
# stripe_version: '2025-09-30.preview' }
)
{client_secret: intent.client_secret}.to_json
end
Submit the payment to Stripe Client-side
Use stripe.confirmPayment to complete the payment using details from the Payment Element.
Provide the confirmation_token parameter with the ID of the ConfirmationToken you created on the previous page, which contains the payment information collected from the Payment Element.
Provide a return_url to this function to indicate where Stripe redirects the user after they complete the payment. Your user might be initially redirected to an intermediate site, such as a bank authorization page, before being redirected to the return_url. Card payments immediately redirect to the return_url when a payment is successful.
If you don’t want to redirect for card payments after payment completion, you can set redirect to if_required. This only redirects customers that check out with redirect-based payment methods.
checkout.js
Disclose Stripe to your customers
Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You’re responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.
