Collect payment details before creating an Intent
Build an integration where you can render the Payment Element prior to creating a PaymentIntent or SetupIntent.
Compare Customers v1 and Accounts v2 references
If your Connect platform uses customer-configured Accounts, use our guide to replace Customer and event references in your code with the equivalent Accounts v2 API references.
The Payment Element allows you to accept multiple payment methods using a single integration. In this integration, learn how to build a custom payment flow where you render the Payment Element, create the PaymentIntent, and confirm the payment from the buyer’s browser. If you prefer to confirm the payment from the server instead, see Finalize payments on the server.
Set up StripeServer-side
First, create a Stripe account or sign in.
Use our official libraries to access the Stripe API from your application:
Enable payment methods
Caution
This integration path doesn’t support BLIK or pre-authorized debits that use the Automated Clearing Settlement System (ACSS). You also can’t use customer_ with dynamic payment methods when the deferred intent is created client-side. The client-side deferred-intent flow can’t include a Customer, and customer_ requires a Customer on the PaymentIntent, so it’s excluded to avoid errors. To use customer_, create the PaymentIntent server-side with a Customer and return its client_ 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 detailsClient-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.
<head> <title>Checkout</title> <script src="https://js.stripe.com/clover/stripe.js"></script> </head>
Create an instance of Stripe with the following JavaScript on your checkout page:
// 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:
<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.
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.
Create a PaymentIntentServer-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. 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.
require 'stripe' Stripe.api_key =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 Customer ID. customer: customer.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'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
Setting `payment_method_options` for `us_bank_account` payments
Setting payment_ at this step won’t affect collected payment methods. Instead, configure payment methods in the Dashboard.
Submit the payment to StripeClient-side
Use stripe.confirmPayment to complete the payment using details from the Payment Element.
Provide a return_url to this function to indicate where Stripe should redirect the user after they complete the payment. Your user might be initially redirected to an intermediate site, like a bank authorization page, before being redirected to the return_. Card payments immediately redirect to the return_ when a payment is successful.
If you don’t want to redirect for card payments after payment completion, you can set redirect to if_. This only redirects customers that check out with redirect-based payment methods.
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 PaymentIntent and obtain clientSecret const res = await fetch("/create-intent", { method: "POST", }); const {client_secret: clientSecret} = await res.json(); // Confirm the PaymentIntent using the details collected by the Payment Element const {error} = await stripe.confirmPayment({ elements, clientSecret, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point is only reached if there's an immediate error when // confirming the payment. Show the error to your customer (for example, payment details incomplete) handleError(error); } else { // Your customer is redirected to your `return_url`. For some payment // methods like iDEAL, your customer is redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } });