Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer tools
Overview
Get started with Connect
Integration fundamentals
Example integrations
Onboard accounts
Configure account Dashboards
Accept payments
    Create a charge
      Direct charges
        Fee configurations for connected accounts
        Reports for direct charge payment fees
        Share payment methods across multiple accounts
      Destination charges
      Separate charges and transfers
    Set statement descriptors
    Set MCCs
    Handle multiple currencies
    Create payment links with Connect
    Use Radar with Connect
    Disputes on Connect
    Create subscriptions
    Create invoices
    Multiple payment method configurations
    Embed the payment method settings component
    Account balance
Pay out to accounts
Manage your Connect platform
Tax forms for your Connect platform
Work with connected account types
HomePlatforms and marketplacesAccept paymentsCreate a charge

Create direct charges

Create charges directly on the connected account and collect fees.

Copy page

Create direct charges when customers transact directly with a connected account, often unaware of your platform’s existence. With direct charges:

  • The payment appears as a charge on the connected account, not your platform’s account.
  • The connected account’s balance increases with every charge.
  • Your account balance increases with application fees from every charge.

This charge type is best suited for platforms providing software as a service. For example, Shopify provides tools for building online storefronts, and Thinkific enables educators to sell online courses.

Note

We recommend using direct charges for connected accounts that have access to the full Stripe Dashboard.

Build a custom payments integration by embedding UI components on your site, using Stripe Elements. The client-side and server-side code builds a checkout form that accepts various payment methods. See how this integration compares to Stripe’s other integration types.

Customer location
Size
Theme
Layout
To see how Link works for a returning user, enter the email demo@stripe.com. To see how Link works during a new signup, enter any other email and complete the rest of the form. This demo only displays Google Pay or Apple Pay if you have an active card with either wallet.

Integration effort

Some code

Integration type

Combine UI components into a custom payment flow

UI customization

CSS-level customization with the Appearance API

First, register for a Stripe account.

Use our official libraries to access the Stripe API from your application:

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

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.

The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe automatically pull payment methods from your Dashboard settings or you can list them manually.

Unless your integration requires a code-based option for offering payment methods, don’t list payment methods manually. Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Stripe prioritizes payment methods that help increase conversion and are most relevant to the currency and the customer’s location. Stripe hides lower priority payment methods in an overflow menu.

Create a PaymentIntent on your server with an amount and currency. In the latest version of the API, specifying the automatic_payment_methods parameter is optional because Stripe enables its functionality by default. You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transaction’s amount, currency, and payment flow.

Command Line
cURL
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -H "Stripe-Account:
{{CONNECTED_ACCOUNT_ID}}
"
\ -d amount=1000 \ -d currency=usd \ -d "automatic_payment_methods[enabled]"=true \ -d application_fee_amount=123

Retrieve the client secret

The PaymentIntent includes a client secret that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side.

Retrieve the client secret from an endpoint on your server, using the browser’s fetch function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret:

main.rb
Ruby
get '/secret' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end

And then fetch the client secret with JavaScript on the client side:

(async () => { const response = await fetch('/secret'); const {client_secret: clientSecret} = await response.json(); // Render the form using the clientSecret })();

Collect payment details
Client-side

Collect payment details on the client with the Payment Element. The Payment Element is a prebuilt UI component that simplifies collecting payment details for a variety of payment methods.

The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation. If you do choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the allow attribute set to equal "payment *".

The checkout page address 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

Install React Stripe.js and the Stripe.js loader from the npm public registry:

Command Line
npm install --save @stripe/react-stripe-js @stripe/stripe-js

Add and configure the Elements provider to your payment page

To use the Payment Element component, wrap your checkout page component in an Elements provider. Call loadStripe with your publishable key, and pass the returned Promise along with the client secret from the previous step as options in the Elements provider.

index.jsx
import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component's render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe(
"pk_test_TYooMQauvdEDq54NiTphI7jx"
, { stripeAccount:
'{{CONNECTED_ACCOUNT_ID}}'
}); function App() { const options = { // pass the client secret from the previous step clientSecret: '{{CLIENT_SECRET}}', // Fully customizable with the Appearance API appearance: {/*...*/}, }; return ( <Elements stripe={stripePromise} options={options}> <CheckoutForm /> </Elements> ); }; ReactDOM.render(<App />, document.getElementById('root'));

Add the PaymentElement component

Use the PaymentElement component to build your form.

CheckoutForm.jsx
import React from 'react'; import {PaymentElement} from '@stripe/react-stripe-js'; const CheckoutForm = () => { return ( <form> <PaymentElement /> <button>Submit</button> </form> ); }; export default CheckoutForm;

The Payment Element renders a dynamic form that allows your customer to pick a payment method type. The form automatically collects all necessary payments details for the payment method selected by the customer. You can customize the appearance of the Payment Element to match the design of your site when you configure the Elements provider.

Submit the payment to Stripe
Client-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 may be first redirected to an intermediate site, like a bank authorization page, before being redirected to the return_url. Card payments immediately redirect to the return_url when a payment is successful.

If you don’t want to redirect for card payments after payment completion, you can set redirect to if_required. This only redirects customers that check out with redirect-based payment methods.

To call stripe.confirmPayment from your payment form component, use the useStripe and useElements hooks.

If you prefer traditional class components over hooks, you can instead use an ElementsConsumer.

CheckoutForm.jsx
import React, {useState} from 'react'; import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js'; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) setErrorMessage(error.message); } else { // 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`. } }; return ( <form onSubmit={handleSubmit}> <PaymentElement /> <button disabled={!stripe}>Submit</button> {/* Show error message to your customers */} {errorMessage && <div>{errorMessage}</div>} </form> ) }; export default CheckoutForm;

Make sure the return_url corresponds to a page on your website that provides the status of the payment. When Stripe redirects the customer to the return_url, we provide the following URL query parameters:

ParameterDescription
payment_intentThe unique identifier for the PaymentIntent.
payment_intent_client_secretThe client secret of the PaymentIntent object.

Caution

If you have tooling that tracks the customer’s browser session, you might need to add the stripe.com domain to the referrer exclude list. Redirects cause some tools to create new sessions, which prevents you from tracking the complete session.

Use one of the query parameters to retrieve the PaymentIntent. Inspect the status of the PaymentIntent to decide what to show your customers. You can also append your own query parameters when providing the return_url, which persist through the redirect process.

PaymentStatus.jsx
import React, {useState, useEffect} from 'react'; import {useStripe} from '@stripe/react-stripe-js'; const PaymentStatus = () => { const stripe = useStripe(); const [message, setMessage] = useState(null); useEffect(() => { if (!stripe) { return; } // Retrieve the "payment_intent_client_secret" query parameter appended to // your return_url by Stripe.js const clientSecret = new URLSearchParams(window.location.search).get( 'payment_intent_client_secret' ); // Retrieve the PaymentIntent stripe .retrievePaymentIntent(clientSecret) .then(({paymentIntent}) => { // Inspect the PaymentIntent `status` to indicate the status of the payment // to your customer. // // Some payment methods will [immediately succeed or fail][0] upon // confirmation, while others will first enter a `processing` state. // // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification switch (paymentIntent.status) { case 'succeeded': setMessage('Success! Payment received.'); break; case 'processing': setMessage("Payment processing. We'll update you when payment is received."); break; case 'requires_payment_method': // Redirect your user back to your payment page to attempt collecting // payment again setMessage('Payment failed. Please try another payment method.'); break; default: setMessage('Something went wrong.'); break; } }); }, [stripe]); return message; }; export default PaymentStatus;

Handle post-payment events
Server-side

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide 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.

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.

In addition to handling the payment_intent.succeeded event, we recommend handling these other events when collecting payments with the Payment Element:

EventDescriptionAction
payment_intent.succeededSent when a customer successfully completes a payment.Send the customer an order confirmation and fulfill their order.
payment_intent.processingSent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. It’s followed by either a payment_intent.succeeded or payment_intent.payment_failed event in the future.Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete.
payment_intent.payment_failedSent when a customer attempts a payment, but the payment fails.If a payment transitions from processing to payment_failed, offer the customer another attempt to pay.

Test the integration

Card numberScenarioHow to test
The card payment succeeds and doesn’t require authentication.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The card payment requires authentication.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The card is declined with a decline code like insufficient_funds.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
The UnionPay card has a variable length of 13-19 digits.Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

See Testing for additional information to test your integration.

OptionalEnable additional payment methods

Collect fees

When a payment is processed, your platform can take a portion of the transaction in the form of application fees. You can set application fee pricing in two ways:

  • Use the Platform Pricing Tool to set and test pricing rules. This no-code feature in the Stripe Dashboard is currently only available for platforms responsible for paying Stripe fees.
  • Set your pricing rules in-house, specifying application fees directly in a PaymentIntent. Fees set with this method override the pricing logic specified in the Platform Pricing Tool.

Your platform can take an application fee with the following limitations:

  • The value of application_fee_amount must be positive and less than the amount of the charge. The application fee collected is capped at the captured amount of the charge.
  • There are no additional Stripe fees on the application fee itself.
  • In line with Brazilian regulatory and compliance requirements, platforms based outside of Brazil, with Brazilian connected accounts can’t collect application fees through Stripe.
  • The currency of application_fee_amount depends upon a few multiple currency factors.

The resulting charge’s balance transaction includes a detailed fee breakdown of both the Stripe and application fees. To provide a better reporting experience, an Application Fee is created after the fee is collected. Use the amount property on the application fee object for reporting. You can then access these objects with the Application Fees endpoint.

Earned application fees are added to your available account balance on the same schedule as funds from regular Stripe charges. Application fees are viewable in the Collected fees section of the Dashboard.

Caution

Application fees for direct charges are created asynchronously by default. If you expand the application_fee object in a charge creation request, the application fee is created synchronously as part of that request. Only expand the application_fee object if you must, because it increases the latency of the request.

To access the application fee objects for application fees that are created asynchronously, listen for the application_fee.created webhook event.

Flow of funds with fees

When you specify an application fee on a charge, the fee amount is transferred to your platform’s Stripe account. When processing a charge directly on the connected account, the charge amount—less the Stripe fees and application fee—is deposited into the connected account.

For example, if you make a charge of 10 USD with a 1.23 USD application fee (like in the previous example), 1.23 USD is transferred to your platform account. 8.18 USD (10 USD - 0.59 USD - 1.23 USD) is netted in the connected account (assuming standard US Stripe fees).

Flow of funds for a charge with an application fee

If you process payments in multiple currencies, read how currencies are handled in Connect.

Issue refunds

Just as platforms can create charges on connected accounts, they can also create refunds of charges on connected accounts. Create a refund using your platform’s secret key while authenticated as the connected account.

Application fees are not automatically refunded when issuing a refund. Your platform must explicitly refund the application fee or the connected account—the account on which the charge was created—loses that amount. You can refund an application fee by passing a refund_application_fee value of true in the refund request:

Command Line
cURL
curl https://api.stripe.com/v1/refunds \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -H "Stripe-Account:
{{CONNECTED_ACCOUNT_ID}}
"
\ -d charge=
{{CHARGE_ID}}
\ -d refund_application_fee=true

By default, the entire charge is refunded, but you can create a partial refund by setting an amount value as a positive integer. If the refund results in the entire charge being refunded, the entire application fee is refunded. Otherwise, a proportional amount of the application fee is refunded. Alternatively, you can provide a refund_application_fee value of false and refund the application fee separately.

See also

  • Working with multiple currencies
  • Statement descriptors with Connect
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc