Advanced integration
Learn how to embed a custom Stripe payment form in your website or application. The client- and server-side code builds a checkout form with Stripe’s Web or Mobile elements to let you accept payments. To build a custom integration that goes beyond the basics of this quickstart, see Accept a payment.
To learn about different payment scenarios, such as subscriptions, and other Stripe products, compare payment integrations.
Interested in using Stripe Tax, discounts, shipping, or currency conversion?
We’re developing a Payment Element integration that manages tax, discounts, shipping, and currency conversion. Read the Build a checkout page guide to learn more.
Install the Stripe Ruby library
Install the Stripe ruby gem and require it in your code. Alternatively, if you’re starting from scratch and need a Gemfile, download the project files using the link in the code editor.
Create a PaymentIntent
Add an endpoint on your server that creates a PaymentIntent. A PaymentIntent tracks the customer’s payment lifecycle, keeping track of any failed payment attempts and ensuring the customer is only charged once. Return the PaymentIntent’s client secret in the response to finish the payment on the client.
Configure payment methods
Stripe enables cards and other common payment methods by default with dynamic payment methods. You can update and configure payment methods from the Dashboard with no code required. Stripe filters payment methods based on eligibility and payment method preferences, then orders and displays them by probability based on factors including amount, currency, and buyer location.
Load Stripe.js
Use Stripe.js to remain PCI compliant by ensuring that payment details are sent directly to Stripe without hitting your server. Always load Stripe.js from js.stripe.com to remain compliant. Don’t include the script in a bundle or host it yourself.
Define the payment form
Add one empty placeholder div
to your checkout form for each Element that you’ll mount. Stripe inserts an iframe into each div
to securely collect the customer’s email address and payment information.
Initialize Stripe.js
Initialize Stripe.js with your publishable API key. You’ll use Stripe.js to create the Payment Element and complete the payment on the client.
Fetch a PaymentIntent
Immediately make a request to the endpoint on your server to create a new PaymentIntent as soon as your checkout page loads. The clientSecret
returned by your endpoint is used to complete the payment.
Initialize Stripe Elements
Initialize the Stripe Elements UI library with the client secret. Elements manages the UI components you need to collect payment details.
Create the PaymentElement
Create a PaymentElement and mount it to the placeholder <div>
in your payment form. This embeds an iframe with a dynamic form that displays configured payment method types available from the PaymentIntent, allowing your customer to select a payment method. The form automatically collects the associated payment details for the selected payment method type.
(Optional) Style the Payment Element
Customize the Payment Element UI by creating an appearance object and initializing Elements with it. Use your company’s color scheme and font to make it match with the rest of your checkout page. Use custom fonts (for example, from Google Fonts) by initializing Elements with a font set.
Make sure to open the preview on the right to see your changes live.
Note
Parts of the preview demo might not match your actual checkout page. The above settings represent only a subset of the Appearance object’s variables, and the Appearance object only controls certain attributes of Stripe Elements. You’re responsible for styling the rest of your checkout page.
Handle the submit event
Listen to the form’s submit event to know when to confirm the payment through the Stripe API.
Complete the payment
Call confirmPayment, passing along the Element instance and a return_url to indicate where Stripe redirects the user after they complete the payment. For payments that require authentication, Stripe displays a modal for 3D Secure authentication or redirects the customer to an authentication page, depending on the payment method. After the customer completes the authentication process, they’re redirected to the return_
.
Handle errors
If there are any immediate errors (for example, your customer’s card is declined), Stripe.js returns an error. Show that error message to your customer so they can try again.
Show a payment status message
When Stripe redirects the customer to the return_
, the payment_
query parameter is appended by Stripe.js. Use this to retrieve the PaymentIntent status update and determine what to show to your customer.
Use a webhook
Stripe sends multiple events during the payment process and after the payment is complete. Create an event destination for a webhook endpoint 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. Stripe recommends handling the payment_intent.succeeded, payment_intent.processing, and payment_intent.payment_failed events.
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.
Make a test payment
To verify that your integration works, make a test payment using test payment details.
See your payment in the Dashboard
Navigate to the Stripe Dashboard to see your test payment.
Accept payments and enhance your integration
You’re ready to accept payments with Stripe. Continue with the steps below to add more features.
Calculate and collect the right amount of tax on your Stripe transactions. Before using Stripe Tax, you need to activate it in the Dashboard. Learn more about Stripe Tax and how to add it to your Payments integration.
Stripe can send an email receipt to your customer using your brand logo and color theme, which are configurable in the Dashboard.
Often used by SaaS or e-commerce businesses with recurring customers.
Next steps
Payouts
Learn how to move funds out of your Stripe account into your bank account.
Refunds
Handle requests for refunds by using the Stripe API or Dashboard.
Fulfillment
Create an event destination to send events to your webhook endpoint to fulfill orders after a payment succeeds, and to handle other critical events.
require 'sinatra'require 'stripe'# This is a public sample test API key.# Don’t submit any personally identifiable information in requests made with this key.# Sign in to see your own test API key embedded in code samples.Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'set :static, trueset :port, 4242# Securely calculate the order amountdef calculate_order_amount(_items)# Calculate the order total on the server to prevent# people from directly manipulating the amount on the client_items.sum {|h| h['amount']}end# An endpoint to start the payment processpost '/create-payment-intent' docontent_type 'application/json'data = JSON.parse(request.body.read)# Create a PaymentIntent with amount and currencypayment_intent = Stripe::PaymentIntent.create(amount: calculate_order_amount(data['items']),currency: 'usd',# In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.automatic_payment_methods: {enabled: true,},){clientSecret: payment_intent.client_secret,}.to_jsonend
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Accept a payment</title><meta name="description" content="A demo of a payment on Stripe" /><meta name="viewport" content="width=device-width, initial-scale=1" /><link rel="stylesheet" href="checkout.css" /><script src="https://js.stripe.com/v3/"></script><script src="checkout.js" defer></script></head><body><!-- Display a payment form --><form id="payment-form"><div id="payment-element"><!--Stripe.js injects the Payment Element--></div><button id="submit"><div class="spinner hidden" id="spinner"></div><span id="button-text">Pay now</span></button><div id="payment-message" class="hidden"></div></form></body></html>
// This is a public sample test API key.// Don’t submit any personally identifiable information in requests made with this key.// Sign in to see your own test API key embedded in code samples.const stripe = Stripe("pk_test_TYooMQauvdEDq54NiTphI7jx");// The items the customer wants to buyconst items = [{ id: "xl-tshirt", amount: 1000 }];let elements;initialize();document.querySelector("#payment-form").addEventListener("submit", handleSubmit);// Fetches a payment intent and captures the client secretasync function initialize() {const response = await fetch("/create-payment-intent", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({ items }),});const { clientSecret } = await response.json();const appearance = {theme: 'stripe',};elements = stripe.elements({ appearance, clientSecret });const paymentElementOptions = {layout: "accordion",};const paymentElement = elements.create("payment", paymentElementOptions);paymentElement.mount("#payment-element");}async function handleSubmit(e) {e.preventDefault();setLoading(true);const { error } = await stripe.confirmPayment({elements,confirmParams: {// Make sure to change this to your payment completion pagereturn_url: "http://localhost:4242/complete.html",},});// This point will only be reached if there is an immediate error when// confirming the payment. Otherwise, your customer will be redirected to// your `return_url`. For some payment methods like iDEAL, your customer will// be redirected to an intermediate site first to authorize the payment, then// redirected to the `return_url`.if (error.type === "card_error" || error.type === "validation_error") {showMessage(error.message);} else {showMessage("An unexpected error occurred.");}setLoading(false);}// ------- UI helpers -------function showMessage(messageText) {const messageContainer = document.querySelector("#payment-message");messageContainer.classList.remove("hidden");messageContainer.textContent = messageText;setTimeout(function () {messageContainer.classList.add("hidden");messageContainer.textContent = "";}, 4000);}// Show a spinner on payment submissionfunction setLoading(isLoading) {if (isLoading) {// Disable the button and show a spinnerdocument.querySelector("#submit").disabled = true;document.querySelector("#spinner").classList.remove("hidden");document.querySelector("#button-text").classList.add("hidden");} else {document.querySelector("#submit").disabled = false;document.querySelector("#spinner").classList.add("hidden");document.querySelector("#button-text").classList.remove("hidden");}}
/* Variables */* {box-sizing: border-box;}body {font-family: -apple-system, BlinkMacSystemFont, sans-serif;font-size: 16px;-webkit-font-smoothing: antialiased;display: flex;flex-direction: column;justify-content: center;align-content: center;height: 100vh;width: 100vw;}form {width: 30vw;min-width: 500px;align-self: center;box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);border-radius: 7px;padding: 40px;margin-top: auto;margin-bottom: auto;}.hidden {display: none;}#payment-message {color: rgb(105, 115, 134);font-size: 16px;line-height: 20px;padding-top: 12px;text-align: center;}#payment-element {margin-bottom: 24px;}/* Buttons and links */button {background: #0055DE;font-family: Arial, sans-serif;color: #ffffff;border-radius: 4px;border: 0;padding: 12px 16px;font-size: 16px;font-weight: 600;cursor: pointer;display: block;transition: all 0.2s ease;box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);width: 100%;}button:hover {filter: contrast(115%);}button:disabled {opacity: 0.5;cursor: default;}/* spinner/processing state, errors */.spinner,.spinner:before,.spinner:after {border-radius: 50%;}.spinner {color: #ffffff;font-size: 22px;text-indent: -99999px;margin: 0px auto;position: relative;width: 20px;height: 20px;box-shadow: inset 0 0 0 2px;-webkit-transform: translateZ(0);-ms-transform: translateZ(0);transform: translateZ(0);}.spinner:before,.spinner:after {position: absolute;content: "";}.spinner:before {width: 10.4px;height: 20.4px;background: #0055DE;border-radius: 20.4px 0 0 20.4px;top: -0.2px;left: -0.2px;-webkit-transform-origin: 10.4px 10.2px;transform-origin: 10.4px 10.2px;-webkit-animation: loading 2s infinite ease 1.5s;animation: loading 2s infinite ease 1.5s;}.spinner:after {width: 10.4px;height: 10.2px;background: #0055DE;border-radius: 0 10.2px 10.2px 0;top: -0.1px;left: 10.2px;-webkit-transform-origin: 0px 10.2px;transform-origin: 0px 10.2px;-webkit-animation: loading 2s infinite ease;animation: loading 2s infinite ease;}/* Payment status page */#payment-status {display: flex;justify-content: center;align-items: center;flex-direction: column;row-gap: 30px;width: 30vw;min-width: 500px;min-height: 380px;align-self: center;box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);border-radius: 7px;padding: 40px;opacity: 0;animation: fadeInAnimation 1s ease forwards;}#status-icon {display: flex;justify-content: center;align-items: center;height: 40px;width: 40px;border-radius: 50%;}h2 {margin: 0;color: #30313D;text-align: center;}a {text-decoration: none;font-size: 16px;font-weight: 600;font-family: Arial, sans-serif;display: block;}a:hover {filter: contrast(120%);}#details-table {overflow-x: auto;width: 100%;}table {width: 100%;font-size: 14px;border-collapse: collapse;}table tbody tr:first-child td {border-top: 1px solid #E6E6E6; /* Top border */padding-top: 10px;}table tbody tr:last-child td {border-bottom: 1px solid #E6E6E6; /* Bottom border */}td {padding-bottom: 10px;}.TableContent {text-align: right;color: #6D6E78;}.TableLabel {font-weight: 600;color: #30313D;}#view-details {color: #0055DE;}#retry-button {text-align: center;background: #0055DE;color: #ffffff;border-radius: 4px;border: 0;padding: 12px 16px;transition: all 0.2s ease;box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);width: 100%;}@-webkit-keyframes loading {0% {-webkit-transform: rotate(0deg);transform: rotate(0deg);}100% {-webkit-transform: rotate(360deg);transform: rotate(360deg);}}@keyframes loading {0% {-webkit-transform: rotate(0deg);transform: rotate(0deg);}100% {-webkit-transform: rotate(360deg);transform: rotate(360deg);}}@keyframes fadeInAnimation {to {opacity: 1;}}@media only screen and (max-width: 600px) {form, #payment-status{width: 80vw;min-width: initial;}}
// ------- UI Resources -------const SuccessIcon =`<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/></svg>`;const ErrorIcon =`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/></svg>`;const InfoIcon =`<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/><path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/></svg>`;// ------- UI helpers -------function setPaymentDetails(intent) {let statusText = "Something went wrong, please try again.";let iconColor = "#DF1B41";let icon = ErrorIcon;if (!intent) {setErrorState();return;}switch (intent.status) {case "succeeded":statusText = "Payment succeeded";iconColor = "#30B130";icon = SuccessIcon;break;case "processing":statusText = "Your payment is processing.";iconColor = "#6D6E78";icon = InfoIcon;break;case "requires_payment_method":statusText = "Your payment was not successful, please try again.";break;default:break;}document.querySelector("#status-icon").style.backgroundColor = iconColor;document.querySelector("#status-icon").innerHTML = icon;document.querySelector("#status-text").textContent= statusText;document.querySelector("#intent-id").textContent = intent.id;document.querySelector("#intent-status").textContent = intent.status;document.querySelector("#view-details").href = `https://dashboard.stripe.com/payments/${intent.id}`;}function setErrorState() {document.querySelector("#status-icon").style.backgroundColor = "#DF1B41";document.querySelector("#status-icon").innerHTML = ErrorIcon;document.querySelector("#status-text").textContent= "Something went wrong, please try again.";document.querySelector("#details-table").classList.add("hidden");document.querySelector("#view-details").classList.add("hidden");}// Stripe.js instanceconst stripe = Stripe("pk_test_TYooMQauvdEDq54NiTphI7jx");checkStatus();// Fetches the payment intent status after payment submissionasync function checkStatus() {const clientSecret = new URLSearchParams(window.location.search).get("payment_intent_client_secret");if (!clientSecret) {setErrorState();return;}const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);setPaymentDetails(paymentIntent);}
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Order Status</title><meta name="viewport" content="width=device-width, initial-scale=1" /><link rel="stylesheet" href="checkout.css" /><script src="https://js.stripe.com/v3/"></script><script src="complete.js" defer></script></head><body><!-- Display the order status --><div id="payment-status"><div id="status-icon"></div><h2 id="status-text"></h2><div id="details-table"><table><tbody><tr><td class="TableLabel">id</td><td id="intent-id" class="TableContent"></td></tr><tr><td class="TableLabel">status</td><td id="intent-status" class="TableContent"></td></tr></tbody></table></div><a href="#" id="view-details" rel="noopener noreferrer" target="_blank">View details<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 3.49998C2.64175 3.49998 2.25 3.89173 2.25 4.37498V11.375C2.25 11.8582 2.64175 12.25 3.125 12.25H10.125C10.6082 12.25 11 11.8582 11 11.375V9.62498C11 9.14173 11.3918 8.74998 11.875 8.74998C12.3582 8.74998 12.75 9.14173 12.75 9.62498V11.375C12.75 12.8247 11.5747 14 10.125 14H3.125C1.67525 14 0.5 12.8247 0.5 11.375V4.37498C0.5 2.92524 1.67525 1.74998 3.125 1.74998H4.875C5.35825 1.74998 5.75 2.14173 5.75 2.62498C5.75 3.10823 5.35825 3.49998 4.875 3.49998H3.125Z" fill="#0055DE"/> <path d="M8.66672 0C8.18347 0 7.79172 0.391751 7.79172 0.875C7.79172 1.35825 8.18347 1.75 8.66672 1.75H11.5126L4.83967 8.42295C4.49796 8.76466 4.49796 9.31868 4.83967 9.66039C5.18138 10.0021 5.7354 10.0021 6.07711 9.66039L12.7501 2.98744V5.83333C12.7501 6.31658 13.1418 6.70833 13.6251 6.70833C14.1083 6.70833 14.5001 6.31658 14.5001 5.83333V0.875C14.5001 0.391751 14.1083 0 13.6251 0H8.66672Z" fill="#0055DE"/></svg></a><a id="retry-button" href="/checkout.html">Test another</a></div></body></html>
source 'https://rubygems.org/'gem 'sinatra'gem 'stripe'
# Accept a PaymentBuild a simple checkout form to collect payment details. Included are some basicbuild and run scripts you can use to start up the application.## Running the sample1. Build the server~~~bundle install~~~2. Run the server~~~ruby server.rb -o 0.0.0.0~~~3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html)