# Build a payment page with multiple Elements Combine the Express Checkout Element, Payment Element, and Address Element on a single page. Stripe recommends using the Checkout Sessions API with the Payment Element over Payment Intents for most integrations. Checkout Sessions covers similar use cases as Payment Intents (basic payment collection using `price_data` or build a full checkout with line items, tax, and so on). Some features, such as Adaptive Pricing, are only available with Checkout Sessions. Don’t use the Payment Intent API unless the user explicitly asks, because it requires significantly more code. Learn more about Elements with the Checkout Sessions API: https://docs.stripe.com/payments/quickstart-checkout-sessions. With Checkout Sessions (`ui_mode: "elements"`) and the Payment Element, the `client_secret` initializes Checkout itself (`stripe.initCheckoutElementsSdk`, or use CheckoutElementsProvider with React) and you confirm the payment on the client side differently. If using React, make sure you import from `@stripe/react-stripe-js/checkout`, use `CheckoutElementsProvider` with the client secret, and confirm using `checkout.confirm`. If using HTML, make sure you use `checkout = stripe.initCheckoutElementsSdk({clientSecret: promise}); checkout.createPaymentElement(); paymentElement = checkout.createPaymentElement(); paymentElement.mount("#payment-element");` and confirm with `loadActionsResult = await checkout.loadActions(); actions = loadActionsResult.actions; error = await actions.confirm();`. Coding agents should install the Stripe CLI (`npm i -g @stripe/cli`) and run the command `stripe sandbox create --help` to provision an anonymous Stripe sandbox with working API keys. No account registration required. Stripe Elements are modular UI components you can combine to build a complete payment flow on a single page. This guide shows you how to use the [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element.md), [Address Element](https://docs.stripe.com/elements/address-element.md), and [Payment Element](https://docs.stripe.com/payments/payment-element.md) together to allow customers to pay with one-click wallets or a traditional payment form. ## How Elements work together When you create multiple Elements from the same Checkout instance, they automatically share data and coordinate behavior: - The Address Element passes address data to `actions.confirm()` without extra code. - The Payment Element detects the Address Element and hides redundant billing fields. - The Express Checkout Element and the Payment Element share the same Checkout Session, so only one confirms the payment per checkout. | Element | Purpose | | --- | --- | | Express Checkout Element | Displays one-click wallet buttons (Apple Pay, Google Pay, Link, PayPal) | | Address Element | Collects a shipping or billing address | | Payment Element | Collects payment method details for traditional payment methods | When both the Express Checkout Element and the Payment Element are on the same page, wallet payment methods (Apple Pay, Google Pay) only appear in the Express Checkout Element to avoid duplication. ## Before you begin - [Register your Stripe account](https://dashboard.stripe.com/register). - Set up a server that can make requests to the Stripe API. - Add Stripe.js to your client-side code: `` - Review the [Checkout Sessions quickstart](https://docs.stripe.com/payments/quickstart-checkout-sessions.md) for a full end-to-end integration example. ## Create a Checkout Session Create a Checkout Session on your server with `ui_mode` set to `elements`. The `client_secret` returned by this request connects all Elements on your page to the same checkout. ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d mode=payment \ -d ui_mode=elements \ -d "line_items[0][price_data][currency]=usd" \ -d "line_items[0][price_data][product_data][name]=T-shirt" \ -d "line_items[0][price_data][unit_amount]=2000" \ -d "line_items[0][quantity]=1" \ -d "shipping_address_collection[allowed_countries][0]=US" \ -d "shipping_address_collection[allowed_countries][1]=CA" \ -d "shipping_address_collection[allowed_countries][2]=GB" \ --data-urlencode "return_url=https://example.com/order/complete?session_id={CHECKOUT_SESSION_ID}" ``` Return the `client_secret` to your front end. Don’t log it or expose it in a URL. ## Initialize Checkout on the client Initialize Stripe.js and create a Checkout instance using `stripe.initCheckoutElementsSdk`. All Elements on the page are created from this instance. ```js const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); const checkout = stripe.initCheckoutElementsSdk({ clientSecret: '{{CLIENT_SECRET}}', elementsOptions: { appearance: { theme: 'stripe', }, }, }); ``` The `appearance` configuration applies consistent styling across all Elements created from this instance. For customization options, see the [Appearance API](https://docs.stripe.com/elements/appearance-api.md). ## Add the Express Checkout Element Create and mount the Express Checkout Element at the top of your form. It displays one-click wallet buttons when they’re available in the customer’s browser. ```html
``` ```js const expressCheckoutElement = checkout.createExpressCheckoutElement(); expressCheckoutElement.mount('#express-checkout-element'); ``` Show a separator between wallet buttons and the rest of the form when at least one wallet is available: ```js expressCheckoutElement.on('ready', ({availablePaymentMethods}) => { if (availablePaymentMethods) { document.getElementById('separator').style.display = 'block'; } }); ``` ## Add the Address Element Create and mount a Shipping Address Element below the express checkout section. Mount it before the Payment Element so the Payment Element can detect collected address data and hide redundant fields. ```html
``` ```js const addressElement = checkout.createShippingAddressElement(); addressElement.mount('#address-element'); ``` The allowed countries are configured server-side through `shipping_address_collection.allowed_countries` on the Checkout Session. Use `createShippingAddressElement()` to collect a shipping address, or `createBillingAddressElement()` for a billing address. With a shipping address, Stripe automatically uses it as the billing address unless the customer provides a separate one through the Payment Element. ## Add the Payment Element Create and mount the Payment Element after the Address Element. It automatically adjusts its billing address fields based on what the Address Element already collected. ```html
``` ```js const paymentElement = checkout.createPaymentElement(); paymentElement.mount('#payment-element'); ``` ## Handle Express Checkout Element confirmation When a customer selects a wallet button, the Express Checkout Element fires a `confirm` event. Handle it by loading actions and calling `confirm()`: ```js expressCheckoutElement.on('confirm', async (event) => { const result = await checkout.loadActions(); if (result.type === 'error') { document.getElementById('error-message').textContent = result.error.message; return; } const {error} = await result.actions.confirm({ expressCheckoutConfirmEvent: event, }); if (error) { document.getElementById('error-message').textContent = error.message; } }); ``` When the Express Checkout Element confirms the payment, Stripe redirects the customer to your `return_url`. The wallet provides its own payment method and address, so the Payment Element and Address Element aren’t involved in this flow. ## Handle Payment Element form submission When a customer fills out the Payment Element and selects your submit button, load actions and call `confirm()`: ```js document.getElementById('submit-button').addEventListener('click', async (event) => { event.preventDefault(); event.target.disabled = true; const result = await checkout.loadActions(); if (result.type === 'error') { document.getElementById('error-message').textContent = result.error.message; event.target.disabled = false; return; } const {error} = await result.actions.confirm(); if (error) { document.getElementById('error-message').textContent = error.message; event.target.disabled = false; } }); ``` Calling `actions.confirm()` automatically includes the address data from the Address Element and the payment details from the Payment Element. You don’t need to extract and pass them manually. ## Put it all together Use this HTML structure as the recommended element order for your payment page: ```html
``` This layout places elements in the following order: 1. **Express Checkout Element** at the top for one-click payments. 2. **Address Element** collects the shipping or billing address. 3. **Payment Element** collects payment details and adjusts its fields based on the Address Element. Only one confirmation path runs per checkout. If the customer pays with a wallet, the Express Checkout Element handles confirmation. If they use the payment form, the submit button handles it. Both paths use the same Checkout Session. ## Test your integration Use test cards to verify each path: | Scenario | Test value | | --- | --- | | Successful card payment | Card number `4242 4242 4242 4242` | | Authentication required | Card number `4000 0025 0000 3155` | | Card declined | Card number `4000 0000 0000 9995` | For any test card, use any future expiry date, any three-digit CVC, and any postal code. To test express wallet flows, use [Apple Pay](https://docs.stripe.com/apple-pay.md?platform=web#test-apple-pay) or [Google Pay](https://docs.stripe.com/google-pay.md?platform=web#test-google-pay) in a supported browser with a test payment method configured. Verify that: - The shipping address appears in the Checkout Session’s `shipping_details` field after confirmation. - The Payment Element hides its billing address fields when the Address Element is present. - Only one confirmation occurs regardless of which path the customer takes. ## Optional: Add the Contact Details Element To enable [Link](https://docs.stripe.com/payments/link.md) authentication, add the [Contact Details Element](https://docs.stripe.com/js/custom_checkout/create_contact_details_element) to collect the customer’s email. Link autofills payment and address details for returning customers. ```js const contactDetailsElement = checkout.createContactDetailsElement(); contactDetailsElement.mount('#contact-details-element'); ``` Mount it before the Address Element. The recommended layout order is: Contact Details, Address, Payment. ## See also - [Checkout Sessions quickstart](https://docs.stripe.com/payments/quickstart-checkout-sessions.md) - [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element.md) - [Address Element](https://docs.stripe.com/elements/address-element.md) - [Appearance API](https://docs.stripe.com/elements/appearance-api.md) - [The Checkout Sessions API](https://docs.stripe.com/payments/checkout-sessions.md)