Sign in
Create account
Sign in
Home
Payments
Business operations
Financial services
Developer tools
Security
All products
Home
Payments
Business operations
Home
Payments
Business operations
Financial services
Developer tools
Support
Overview
Overview
Get started
Collect payments then pay out
Enable other businesses to accept payments directly
Pay out money
Explore Connect
Onboard your accounts
Choose your account type
Standard
Express
Custom
Service agreement types
Account capabilities
Update verified info
Accept payments
Create a charge
Create a payments page
Add payment methods
Set statement descriptors
Clone customers across accounts
Create subscriptions
Create invoices
Debit connected accounts
Pay out
Set bank and debit card payouts
Bank accounts
Manage payout schedule
Manual payouts
Payout reversals
Instant Payouts
Cross-border payouts
Manage funds
Add money to your platform balance
Account balance
Handle other currencies
Manage accounts
Best practices
Listen for updates
Dashboard account management
Platform controls for Standard accounts
Make API calls for connected accounts
Set MCCs
Testing
Manage tax forms
Overview
Get started with tax reporting
Tax form settings
Calculation methods
File tax forms
File tax forms with states
Modify tax forms
Deliver tax forms
Correct tax forms
Split tax forms
Tax reporting for Payable users
Testing
connect
·
HomePaymentsMultiparty payments

Collect payments then pay out

Collect payments from customers and pay them out to sellers or service providers.

This guide demonstrates how to accept payments and move funds to the bank accounts of your sellers or service providers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to people looking for a place to rent. You can use the concepts covered in this guide in other applications as well.

You can see a complete onboarding flow in action in our sample end-to-end Express integration.

Prerequisites

  1. Register your platform.
  2. Activate your account.
  3. Fill out your platform profile.
  4. Customize your brand settings on the Connect settings page. This information is required for Connect Onboarding.

Set up Stripe
Server-side

Install Stripe’s official libraries so you can access the API from your application:

Terminal
# Available as a gem sudo gem install stripe
Gemfile
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Create a connected account

When a user (seller or service provider) signs up on your platform, create a user Account (referred to as a connected account) so you can accept payments and move funds to their bank account. Connected accounts represent your users in the Stripe API and help collect the information required to verify the user’s identity. In our home-rental example, the connected account represents the homeowner.

What you're building
Connect with
The Connect button is available as an image file or code snippet.

Step 2.1: Create an Express account and prefill information

Use the /v1/accounts API to create an Express account and set type to express in the account creation request.

Terminal
curl https://api.stripe.com/v1/accounts \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "type"="express"

If you already collected information for your connected accounts, you can prefill that information on the account object for the user and it won’t be collected again in the Connect Onboarding flow. Connect Onboarding only collects required information when you create or update an account.

Step 2.2: Create an account link

You can create an account link by calling the Account Links API with the following parameters:

  • account
  • refresh_url
  • return_url
  • type = account_onboarding
Terminal
curl https://api.stripe.com/v1/account_links \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "account"="acct_1032D82eZvKYlo2C" \ -d "refresh_url"="https://example.com/reauth" \ -d "return_url"="https://example.com/return" \ -d "type"="account_onboarding"

Step 2.3: Redirect your user to the account link URL

The response to your Account Links request includes a value for the key url. Redirect to this link to send your user into the flow. URLs from the Account Links API are temporary and can only be used once, because they grant access to the account holder’s personal information. Authenticate the user in your application before redirecting them to this URL. If you want to prefill information, you must do so before generating the account link. After you create the account link for an Express account, you can’t read or write information for the account.

Don’t email, text, or otherwise send account link URLs directly to your user. Instead, redirect the authenticated user to the account link URL from within your platform’s application.

Step 2.4: Handle the user returning to your platform

Connect Onboarding requires you to pass both a return_url and refresh_url to handle all cases where the user is redirected to your platform. It’s important that you implement these correctly to provide the best experience for your user.

You can use HTTP for your return_url and refresh_url while in test mode (e.g., to test with localhost), but in live mode only HTTPS is accepted. Be sure to swap testing URLs for HTTPS URLs before going live.

return_url

Stripe issues a redirect to this URL when the user completes the Connect Onboarding flow. This doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. This only means the flow was entered and exited properly.

No state is passed through this URL. After a user is redirected to your return_url, check the state of the details_submitted parameter on their account by doing either of the following:

  • Listening to account.updated webhooks
  • Calling the Accounts API and inspecting the returned object

refresh_url

Your user will be redirected to the refresh_url in these cases:

  • The link is expired (a few minutes went by since the link was created).
  • The link was already visited (the user refreshed the page or clicked back or forward in the browser).
  • Your platform is no longer able to access the account.
  • The account has been rejected.

Your refresh_url should trigger a method on your server to call Account Links again with the same parameters, and redirect the user to the Connect Onboarding flow to create a seamless experience.

Step 2.5: Handle users that haven’t completed onboarding

A user that’s redirected to your return_url might not have completed the onboarding process. Use the /v1/accounts endpoint to retrieve the user’s account and check for charges_enabled. If the account isn’t fully onboarded, provide UI prompts to allow the user to continue onboarding later. The user can complete their account activation through a new account link (generated by your integration). You can check the state of the details_submitted parameter on their account to see if they’ve completed the onboarding process.

Accept a payment

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, consider Checkout, a Stripe-hosted page to accept payments for one-time purchases and subscriptions.

What you're building

Step 3.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.

Terminal
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "payment_method_types[]"=card \ -d amount=1000 \ -d currency="usd" \ -d application_fee_amount="123" \ -d "transfer_data[destination]"="{{CONNECTED_STRIPE_ACCOUNT_ID}}"

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 is generally the best approach when your client side is a single-page application, especially one built with a modern frontend framework such as React. This example shows how to create the server endpoint that serves the client secret:

main.rb
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 3.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.

checkout.html
<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:

script.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 var stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); var elements = stripe.elements();

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.

checkout.html
View full sample
<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:

client.js
View full sample
// 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");

More examples

See additional payment forms created with Elements on GitHub.

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.3: Submit the payment to Stripe Client-side

Rather than sending the entire PaymentIntent object to the client, use its client secret from Step 3.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 3.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].

client.js
View full sample
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 3.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.

server.rb
# 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/apikeys Stripe.api_key =
"sk_test_4eC39HqLyjWDarjtT1zdp7dc"
# 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

Learn more in our fulfillment guide for payments.

Testing webhooks locally

Testing webhooks locally is easy with the Stripe CLI.

  1. First, install the Stripe CLI on your machine if you haven’t already.

  2. Then, to log in run stripe login in the command line, and follow the instructions.

  3. 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 run stripe trigger --stripe-account={{CONNECTED_STRIPE_ACCOUNT_ID}} payment_intent.succeeded (or trigger any other supported event) in another.

Step 3.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.

Complete and customize your integration

You now have a working integration. From your account dashboard, you can view an account and its balance.

You can review other Connect features that you might want to consider adding to the integration.

Payouts

By default, any charge that you transfer to a connected account accumulates in the connected account’s Stripe balance and is paid out on a daily rolling basis. But, you can change the payout schedule if needed.

Testing

The Connect-specific testing resource provides tokens to help simulate flows for accounts and onboarding, payouts, and top-ups. To test your payments and disputes flow, we provide a number of test cards available to simulate payment outcomes.

Next steps

Manage connected accounts

  • Use your Stripe dashboard
  • Best practices, including detecting fraud and providing user support
  • Onboard connected accounts outside of your platform’s country
  • Integrate with the Express dashboard
  • Collect information required for US taxes

Customize payments

  • Issue refunds
  • Customize statement descriptors
  • Share customers across connected accounts
  • Work with multiple currencies
  • Debit a connected account
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.
On this page
Prerequisites
Set up Stripe
Create a connected account
Accept a payment
Complete and customize your integration