Create a payments page
You can create a payments page to accept payments for your users. Refer to your platform profile to determine if direct charges or destination charges is recommended for your business.
Use either Stripe Elements or Checkout to create your payments page. Stripe Elements is a set of prebuilt UI components, like inputs and buttons, for building your checkout flow. If you’d rather not build your own payment form, Checkout provides a Stripe-hosted page to accept payments for one-time purchases and subscriptions.
Destination charges
For demonstrative purposes, the platform will represent a home-rental marketplace that’s creating payments for homeowners renting their places. You can use destination charges in other applications as well.
Step 1: Create a PaymentIntent Server-side
Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.
Create a PaymentIntent on your server with an amount and currency. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -d "payment_method_types[]"=card \ -d amount=1000 \ -d currency="usd" \ -d application_fee_amount="123" \ -d "transfer_data[destination]"="{{CONNECTED_STRIPE_ACCOUNT_ID}}" \sk_test_4eC39HqLyjWDarjtT1zdp7dc
In our home-rental example, we want to build an experience where customers pay for rentals by using our platform, and where we pay homeowners for renting to customers. To set this experience up:
- Indicate the rental is a destination charge with
transfer_data[destination]
. - Specify how much of the rental amount will go to the platform with
application_fee_amount
.
When a rental charge occurs, Stripe transfers the entire amount to the connected account’s pending balance (transfer_data[destination]
). Afterward, Stripe transfers the fee amount (application_fee_amount
) to the platform’s account, which is the share of the revenue for facilitating the rental. Then, Stripe deducts the Stripe fees from the platform’s fee amount. An illustration of this funds flow is below:
This PaymentIntent creates a destination charge. If you need to control the timing of transfers or need to transfer funds from a single payment to multiple parties, use separate charges and transfers instead.
Included in the returned PaymentIntent is a client secret, which is used on the client side to securely complete the payment process instead of passing the entire PaymentIntent object. There are different approaches that you can use to pass the client secret to the client side.
You can retrieve the client secret from an endpoint on your server using the browser’s fetch
function on the client side. This approach is generally most suitable when your client side is a single-page application, particularly one built with a modern frontend framework such as React. This example shows how to create the server endpoint that serves the client secret:
get '/secret' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end
This example demonstrates how to fetch the client secret with JavaScript on the client side:
var response = fetch('/secret').then(function(response) { return response.json(); }).then(function(responseJson) { var clientSecret = responseJson.client_secret; // Call stripe.confirmCardPayment() with the client secret. });
Step 2: Collect card details Client-side
You’re ready to collect card information on the client with Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, ZIP code, and expiration date.
A Stripe Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.
You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments.
Set up Stripe Elements
Stripe Elements 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. Do not include the script in a bundle or host a copy of it yourself.
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>
Create an instance of Elements 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/account/apikeys var stripe = Stripe(
); var elements = stripe.elements();'pk_test_TYooMQauvdEDq54NiTphI7jx'
Add Elements to your payment page
Elements needs a place to live in your payment form. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements.
<form id="payment-form"> <div id="card-element"> <!-- Elements will create input elements here --> </div> <!-- We'll put the error messages in this element --> <div id="card-errors" role="alert"></div> <button id="submit">Pay</button> </form>
When the form above has loaded, create an instance of an Element and mount it to the Element container:
// Set up Stripe.js and Elements to use in checkout form var elements = stripe.elements(); var style = { base: { color: "#32325d", } }; var card = elements.create("card", { style: style }); card.mount("#card-element");
The card
Element simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine cardNumber
, cardExpiry
, and cardCvc
Elements for a flexible, multi-input card form.
Always collect a postal code to increase card acceptance rates and reduce fraud.
The single input card
Element automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with multi-input card Elements (cardNumber
, cardExpiry
, cardCvc
), add a separate input field for the customer’s postal code.
For a full list of supported Element types, refer to our Stripe.js reference documentation.
Elements validates user input as it is typed. To help your customers catch mistakes, listen to change
events on the card
Element and display any errors:
card.on('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } });
Postal code validation depends on your customer’s billing country. Use our international test cards to experiment with other postal code formats.
Step 3: Submit the payment to Stripe Client-side
Rather than sending the entire PaymentIntent object to the client, use its client secret from Step 1. This is different from your API keys that authenticate Stripe API requests.
The client secret should still be handled carefully because it can complete the charge. Do not log it, embed it in URLs, or expose it to anyone but the customer.
To complete the payment when the user clicks, retrieve the client secret from the PaymentIntent you created in Step 1 and call stripe.confirmCardPayment with the client secret.
Pass additional billing details, such as the cardholder name and address, to the billing_details
hash. The card
Element automatically sends the customer’s postal code information. However, combining cardNumber
, cardCvc
, and cardExpiry
Elements requires you to pass the postal code to billing_details[address][postal_code]
.
var form = document.getElementById('payment-form'); form.addEventListener('submit', function(ev) { ev.preventDefault(); stripe.confirmCardPayment(clientSecret, { payment_method: { card: card, billing_details: { name: 'Jenny Rosen' } } }).then(function(result) { if (result.error) { // Show error to your customer (e.g., insufficient funds) console.log(result.error.message); } else { // The payment has been processed! if (result.paymentIntent.status === 'succeeded') { // Show a success message to your customer // There's a risk of the customer closing the window before callback // execution. Set up a webhook or plugin to listen for the // payment_intent.succeeded event that handles any business critical // post-payment actions. } } }); });
stripe.confirmCardPayment
may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator.
If the customer must authenticate the card, Stripe.js walks them through that process by showing them a modal. You can see an example of this modal experience by using the test card number with any CVC, future expiration date, and postal code in the demo at the top of the page.
When the payment completes successfully, the value of the returned PaymentIntent’s status
property is succeeded. Check the status of a PaymentIntent in the Dashboard or by inspecting the status property on the object. If the payment is not successful, inspect the returned error
to determine the cause.
Step 4: Fulfillment Server-side
After the payment is completed, you’ll need to handle any fulfillment necessary on your end. A home-rental company that requires payment upfront, for instance, would connect the homeowner with the renter after a successful payment.
Configure a webhook endpoint (for events from your account) in your dashboard.
Then create an HTTP endpoint on your server to monitor for completed payments to then enable your sellers or service providers to fulfill purchases.
# Using Sinatra. require 'sinatra' require 'stripe' set :port, 4242 # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key =
# Uncomment and replace with a real secret. You can find your endpoint's # secret in your webhook settings. # webhook_secret = 'whsec_...' post '/webhook' do payload = request.body.read sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil # Verify webhook signature and extract the event. # See https://stripe.com/docs/webhooks/signatures for more information. begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload. status 400 return rescue Stripe::SignatureVerificationError => e # Invalid Signature. status 400 return end if event['type'] == 'payment_intent.succeeded' payment_intent = event['data']['object'] handle_successful_payment_intent(payment_intent) end status 200 end def handle_successful_payment_intent(payment_intent) # Fulfill the purchase. puts payment_intent.to_s end"sk_test_4eC39HqLyjWDarjtT1zdp7dc"
Learn more in our fulfillment guide for payments.
Testing webhooks locally
Testing webhooks locally is easy with the Stripe CLI.
First, install the Stripe CLI on your machine if you haven’t already.
Then, to log in run
stripe login
in the command line, and follow the instructions.Finally, to allow your local host to receive a simulated event on your connected account run
stripe listen --forward-to localhost:{PORT}/webhook
in one terminal window, and runstripe trigger --stripe-account={{CONNECTED_STRIPE_ACCOUNT_ID}} payment_intent.succeeded
(or trigger any other supported event) in another.
Step 5: Disputes
As the settlement merchant on charges, your platform is responsible for disputes. Make sure you understand the best practices for responding to disputes.
Subscriptions
You can create a recurring payment on a connected account with subscriptions. Subscriptions are created with direct charges. Optionally, specify an application_fee_percent to direct a percentage of each invoice payment to your platform.
The Checkout page is branded using the business name, icon, logo, and color of the connected account.
It is possible to include a combination of one-time line items and recurring plans in a Checkout Session. If specified, application_fee_percent
will apply to both one-time and recurring items.
Step 1: Create a Checkout session Server-side
On your server, make the following call to Stripe’s API:
curl https://api.stripe.com/v1/checkout/sessions \ -u
: \ -d "payment_method_types[]"="card" \ -d "line_items[][price]"="{{PRICE_ID}}" \ -d "line_items[][quantity]"=1 \ -d "subscription_data[application_fee_percent]"=10 \ -d "mode"="subscription" \ -d "success_url"="https://example.com/success" \ -d "cancel_url"="https://example.com/cancel" \ -H "Stripe-Account: {{CONNECTED_STRIPE_ACCOUNT_ID}}"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Step 2: Add a checkout button Client-side
On your checkout page, include the Stripe.js script by adding it to the head
of your HTML file.
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>
Fetch the Session ID from your server. Add a button to your client with a click handler that calls Stripe’s frontend API:
// Initialize Stripe.js with the same connected account ID used when creating // the Checkout Session. var stripe = Stripe(
, { stripeAccount: '{{CONNECTED_STRIPE_ACCOUNT_ID}}' }); var checkoutButton = document.getElementById('checkout-button'); checkoutButton.addEventListener('click', function() { stripe.redirectToCheckout({ // Make the id field from the Checkout Session creation API response // available to this file, so you can provide it as argument here // instead of the {{CHECKOUT_SESSION_ID}} placeholder. sessionId: '{{CHECKOUT_SESSION_ID}}' }).then(function (result) { // If `redirectToCheckout` fails due to a browser or network // error, display the localized error message to your customer // using `result.error.message`. }); });'pk_test_TYooMQauvdEDq54NiTphI7jx'
Branding
Your platform and Standard connected accounts can use the Branding settings in the Dashboard to customize branding on the payments page. Destination charges will use the platform’s branding. For direct charges, platforms can use the Accounts API to configure the branding settings for Express and Custom accounts.
The account update API accepts the following parameters for branding:
icon
- Displayed next to the business name in the header of the Checkout page.logo
- If specified, displayed instead of the icon and business name in the header of the Checkout page.primary_color
- Used as the background color on the Checkout page.secondary_color
- Used as the button color on the Checkout page.
curl https://api.stripe.com/v1/accounts/{{CONNECTED_STRIPE_ACCOUNT_ID}} \ -u
: \ -d "settings[branding][icon]"="file_123" \ -d "settings[branding][logo]"="file_456" \ -d "settings[branding][primary_color]"="#663399" \ -d "settings[branding][secondary_color]"="#4BB543"sk_test_4eC39HqLyjWDarjtT1zdp7dc