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
      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 separate charges and transfers

Create charges on your platform account and transfer funds to multiple connected accounts.

Copy page

Create separate charges and transfers to transfer funds from one payment to multiple connected accounts, or when a specific user isn’t known at the time of the payment. The charge on your platform account is decoupled from the transfers to your connected accounts. With this charge type:

  • You create a charge on your platform’s account and also transfer funds to your connected accounts. The payment appears as a charge on your account and there are also transfers to connected accounts (amount determined by you), which are withdrawn from your account balance.
  • You can transfer funds to multiple connected accounts.
  • Your account balance is debited for the cost of the Stripe fees, refunds, and chargebacks.

This charge type is most optimal for marketplaces that need to split payments between multiple parties, such as DoorDash, a restaurant delivery platform.

Stripe supports separate charges and transfers in the following regions:

Australia
Austria
Belgium
Brazil
Bulgaria
Canada
Croatia
Cyprus
Czech Republic
Denmark
Estonia
Finland
France
Germany
Greece
Hungary
Ireland
Italy
Japan
Latvia
Liechtenstein
Lithuania
Luxembourg
Malaysia
Malta
Mexico
Netherlands
New Zealand
Norway
Poland
Portugal
Romania
Singapore
Slovakia
Slovenia
Spain
Sweden
Switzerland
United Kingdom
United States

In most scenarios, your platform and any connected account must be in the same region. Attempting to transfer funds across a disallowed border returns an error. For information about cross-region support, see cross-border transfers. You must only use transfers in combination with the permitted use cases for charges, tops-ups and fees. We recommend using separate charges and transfers for connected accounts that have access to the Express Dashboard or no Dashboard access.

Private preview

You can use destination charges, without the on_behalf_of parameter, when your platform and connected account are in different countries within the US, UK, and EU. For early access, contact us.

Integrate Stripe’s pre-built payment UI into the checkout of your Android app with the PaymentSheet class.

Set up Stripe
Server-side
Client-side

First, you need a Stripe account. Register now.

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use the official libraries for access to the Stripe API from your server:

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'

Client-side

The Stripe Android SDK is open source and fully documented.

To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

build.gradle.kts
Kotlin
plugins { id("com.android.application") } android { ... } dependencies { // ... // Stripe Android SDK implementation("com.stripe:stripe-android:21.16.0") // Include the financial connections SDK to support US bank account as a payment method implementation("com.stripe:financial-connections:21.16.0") }

Note

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

Kotlin
import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext,
"pk_test_TYooMQauvdEDq54NiTphI7jx"
) } }

Note

Use your test keys while you test and develop, and your live mode keys when you publish your app.

Add an endpoint
Server-side

This integration uses three Stripe API objects:

  1. A PaymentIntent. Stripe uses this to represent your intent to collect a payment from a customer, tracking your charge attempts and payment state changes throughout the process.
  2. A Customer(optional). To set up a payment method for future payments, it must be attached to a Customer. Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customer’s account later.
  3. A Customer Ephemeral Key (optional). Information on the Customer object is sensitive, and can’t be retrieved directly from an app. An Ephemeral Key grants the SDK temporary access to the Customer.

If you want to save cards and allow returning customers to reuse saved cards, you need the Customer and Customer Ephemeral Key objects for your integration. Otherwise, you can omit these objects.

For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that:

  1. Retrieves the Customer, or creates a new one.
  2. Creates an Ephemeral Key for the Customer.
  3. Creates a PaymentIntent with the amount, currency, customer, and a transfer group to associate with the transfer of funds later.
  4. Returns the Payment Intent’s client secret, the Ephemeral Key’s secret, the Customer ID, and your publishable key to your app.

The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe 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 prioritises payment methods that help increase conversion and are most relevant to the currency and the customer’s location. We hide lower priority payment methods in an overflow menu.

You can find a running implementation of this endpoint available on Glitch for quick testing.

Command Line
curl
# Create a Customer (use an existing Customer ID if this is a returning customer) curl https://api.stripe.com/v1/customers \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -X "POST" # Create an Ephemeral Key for the Customer curl https://api.stripe.com/v1/ephemeral_keys \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -H "Stripe-Version: 2025-05-28.basil" \ -X "POST" \ -d "customer"=
{{CUSTOMER_ID}}
\ # Create a PaymentIntent curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -X "POST" \ -d "customer"=
{{CUSTOMER_ID}}
\ -d "amount"=10000 \ -d "currency"="usd" \ # In the latest version of the API, specifying the `automatic_payment_methods` parameter # is optional because Stripe enables its functionality by default. -d "automatic_payment_methods[enabled]"=true \ -d transfer_group="ORDER100" \

Integrate the payment sheet
Client-side

Before displaying the mobile Payment Element, your checkout page should:

  • Show the products being purchased and the total amount
  • Collect any required shipping information using the Address Element
  • Include a checkout button to present Stripe’s UI

Initialise a PaymentSheet instance inside onCreate of your checkout Activity, passing a method to handle the result.

import androidx.compose.runtime.Composable import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.rememberPaymentSheet @Composable fun App() { val paymentSheet = rememberPaymentSheet(::onPaymentSheetResult) } private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // implemented in the next steps }

Next, fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set the publishable key using PaymentConfiguration and store the others for use when you present the PaymentSheet.

import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext // Add the following lines to build.gradle to use this example's networking library: // implementation 'com.github.kittinunf.fuel:fuel:2.3.1' // implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1' import com.github.kittinunf.fuel.httpPost import com.github.kittinunf.fuel.json.responseJson import com.stripe.android.PaymentConfiguration import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.rememberPaymentSheet import com.github.kittinunf.result.Result @Composable fun App() { val paymentSheet = rememberPaymentSheet(::onPaymentSheetResult) val context = LocalContext.current var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) } var paymentIntentClientSecret by remember { mutableStateOf<String?>(null) } LaunchedEffect(context) { "Your backend endpoint/payment-sheet".httpPost().responseJson { _, _, result -> if (result is Result.Success) { val responseJson = result.get().obj() paymentIntentClientSecret = responseJson.getString("paymentIntent") customerConfig = PaymentSheet.CustomerConfiguration( id = responseJson.getString("customer"), ephemeralKeySecret = responseJson.getString("ephemeralKey") ) val publishableKey = responseJson.getString("publishableKey") PaymentConfiguration.init(context, publishableKey) } } } } private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // implemented in the next steps }

When the customer taps your checkout button, call presentWithPaymentIntent to present the payment sheet. After the customer completes the payment, the sheet dismisses and the PaymentSheetResultCallback is called with a PaymentSheetResult.

import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext // Add the following lines to build.gradle to use this example's networking library: // implementation 'com.github.kittinunf.fuel:fuel:2.3.1' // implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1' import com.github.kittinunf.fuel.httpPost import com.github.kittinunf.fuel.json.responseJson import com.stripe.android.PaymentConfiguration import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.rememberPaymentSheet import com.github.kittinunf.result.Result @OptIn(ExperimentalCustomerSessionApi::class) @Composable fun App() { val paymentSheet = rememberPaymentSheet(::onPaymentSheetResult) val context = LocalContext.current var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) } var paymentIntentClientSecret by remember { mutableStateOf<String?>(null) } LaunchedEffect(context) { "Your backend endpoint/payment-sheet".httpPost().responseJson { _, _, result -> if (result is Result.Success) { val responseJson = result.get().obj() paymentIntentClientSecret = responseJson.getString("paymentIntent") customerConfig = PaymentSheet.CustomerConfiguration( id = responseJson.getString("customer"), ephemeralKeySecret = responseJson.getString("ephemeralKey") ) val publishableKey = responseJson.getString("publishableKey") PaymentConfiguration.init(context, publishableKey) } } } Button( onClick = { val currentConfig = customerConfig val currentClientSecret = paymentIntentClientSecret if (currentConfig != null && currentClientSecret != null) { presentPaymentSheet(paymentSheet, currentConfig, currentClientSecret) } } ) { Text("Checkout") } } private fun presentPaymentSheet( paymentSheet: PaymentSheet, customerConfig: PaymentSheet.CustomerConfiguration, paymentIntentClientSecret: String ) { paymentSheet.presentWithPaymentIntent( paymentIntentClientSecret, PaymentSheet.Configuration( merchantDisplayName = "My merchant name", customer = customerConfig, // Set `allowsDelayedPaymentMethods` to true if your business handles // delayed notification payment methods like US bank accounts. allowsDelayedPaymentMethods = true ) ) } private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { when(paymentSheetResult) { is PaymentSheetResult.Canceled -> { print("Canceled") } is PaymentSheetResult.Failed -> { print("Error: ${paymentSheetResult.error}") } is PaymentSheetResult.Completed -> { // Display for example, an order confirmation screen print("Completed") } } }

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isn’t known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfil their order (for example, ship their product) when the payment is successful.

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.

Create a Transfer
Server-side

On your server, send funds from your account to a connected account by creating a Transfer and specifying the transfer_group used.

Command Line
cURL
curl https://api.stripe.com/v1/transfers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=7000 \ -d currency=usd \ -d destination=
{{CONNECTED_ACCOUNT_ID}}
\ -d transfer_group=ORDER100

Transfer and charge amounts don’t have to match. You can split a single charge between multiple transfers or include multiple charges in a single transfer. The following example creates an additional transfer associated with the same transfer_group.

Command Line
cURL
curl https://api.stripe.com/v1/transfers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=2000 \ -d currency=usd \ -d destination={{OTHER_CONNECTED_ACCOUNT_ID}} \ -d transfer_group=ORDER100

Transfer options

You can assign any value to the transfer_group string, but it must represent a single business action. You can also make a transfer with neither an associated charge nor a transfer_group – for example, when you must pay a provider but there’s no associated customer payment.

Note

The transfer_group only identifies associated objects. It doesn’t affect any standard functionality. To prevent a transfer from executing before the funds from the associated charge are available, use the transfer’s source_transaction attribute.

By default, a transfer request fails when the amount exceeds the platform’s available account balance. Stripe doesn’t automatically retry failed transfer requests.

You can avoid failed transfer requests for transfers that are associated with charges. When you specify the associated charge as the transfer’s source_transaction, the transfer request automatically succeeds. However, we don’t execute the transfer until the funds from that charge are available in the platform account.

Note

If you use separate charges and transfers, take that into account when planning your payout schedule. Automatic payouts can interfere with transfers that don’t have a defined source_transaction.

Test the integration

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

See Testing for additional information to test your integration.

OptionalEnable Google Pay

OptionalCustomise the sheet

OptionalComplete payment in your UI

OptionalEnable additional payment methods

Specify the settlement merchant

The settlement merchant is dependent on the capabilities set on an account and how a charge is created. The settlement merchant determines whose information is used to make the charge. This includes the statement descriptor (either the platform’s or the connected account’s) that’s displayed on the customer’s credit card or bank statement for that charge.

Specifying the settlement merchant allows you to be more explicit about who to create charges for. For example, some platforms prefer to be the settlement merchant because the end customer interacts directly with their platform (such as on-demand platforms). However, some platforms have connected accounts that interact directly with end customers instead (such as a storefront on an e-commerce platform). In these scenarios, it might make more sense for the connected account to be the settlement merchant.

You can set the on_behalf_of parameter to the ID of a connected account to make that account the settlement merchant for the payment. When using on_behalf_of:

  • Charges settle in the connected account’s country and settlement currency.
  • The fee structure for the connected account’s country is used.
  • The connected account’s statement descriptor is displayed on the customer’s credit card statement.
  • If the connected account is in a different country than the platform, the connected account’s address and phone number are displayed on the customer’s credit card statement.
  • The number of days that a pending balance is held before being paid out depends on the delay_days setting on the connected account.

If on_behalf_of is omitted, the platform is the business of record for the payment.

Caution

The on_behalf_of parameter is supported only for connected accounts with a payments capability such as card_payments. Accounts under the recipient service agreement can’t request card_payments or other payments capabilities.

Command Line
cURL
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=10000 \ -d currency=usd \ -d "automatic_payment_methods[enabled]"=true \ -d on_behalf_of=
{{CONNECTED_ACCOUNT_ID}}
\ -d transfer_group=ORDER100

Collect fees

When using separate charges and transfers, the platform can collect fees on a charge by reducing the amount it transfers to the destination accounts. For example, consider a restaurant delivery service transaction that involves payments to the restaurant and to the driver:

  1. The customer pays a 100 USD charge.
  2. Stripe collects a 3.20 USD fee and adds the remaining 96.80 USD to the platform account’s pending balance.
  3. The platform transfers 70 USD to the restaurant’s connected account and 20 USD to the driver’s connected account.
  4. A platform fee of 6.80 USD remains in the platform account.
How a charge is divided into fees for the platform account and transfers for the connected accounts

To learn about processing payments in multiple currencies with Connect, see working with multiple currencies.

Transfer availability

The default behaviour is to transfer funds from the platform account’s available balance. Attempting a transfer that exceeds the available balance fails with an error. To avoid this problem, when creating a transfer, tie it to an existing charge by specifying the charge ID as the source_transaction parameter. With a source_transaction, the transfer request returns success regardless of your available balance if the related charge hasn’t settled yet. However, the funds don’t become available in the destination account until the funds from the associated charge are available to transfer from the platform account.

Note

If a transfer fails due to insufficient funds in your platform balance, adding funds doesn’t automatically retry the failed action. After adding funds, you must repeat any failed transfers or payouts.

If the source charge has a transfer_group value, Stripe assigns the same value to the transfer’s transfer_group. If it doesn’t, then Stripe generates a string in the format group_ plus the associated PaymentIntent ID, for example: group_pi_2NHDDD589O8KAxCG0179Du2s. It assigns that string as the transfer_group for both the charge and the transfer.

Note

You must specify the source_transaction when you create a transfer. You can’t update that attribute later.

Command Line
cURL
curl https://api.stripe.com/v1/transfers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=7000 \ -d currency=usd \ -d source_transaction=
{{CHARGE_ID}}
\ -d destination=
{{CONNECTED_ACCOUNT_ID}}

You can get the charge ID from the PaymentIntent:

  • Get the PaymentIntent’s latest_charge attribute. This attribute is the ID of the most recent charge associated with the PaymentIntent.
  • Request a list of charges, specifying the payment_intent in the request. This method returns full data for all charges associated with the PaymentIntent.

When using this parameter:

  • The amount of the transfer must not exceed the amount of the source charge
  • You can create multiple transfers with the same source_transaction, as long as the sum of the transfers doesn’t exceed the source charge
  • The transfer takes on the pending status of the associated charge: if the funds from the charge become available in N days, the payment that the destination Stripe account receives from the transfer also becomes available in N days
  • Stripe automatically creates a transfer_group for you
  • The currency of the balance transaction associated with the charge must match the currency of the transfer

Asynchronous payment methods, such as ACH, can fail after a subsequent transfer request is made. For these payments, avoid using source_transaction. Instead, wait until a charge.succeeded event is triggered before transferring the funds. If you have to use source_transaction with these payments, you must implement functionality to manage payment failures.

When a payment used as a source_transaction fails, funds from your platform’s account balance are transferred to the connected account to cover the payment. To recover these funds, reverse the transfer associated with the failed source_transaction.

Issue refunds

You can refund charges created on your platform using its secret key. However, refunding a charge has no impact on any associated transfers. It’s up to your platform to reconcile any amount owed back to it by reducing subsequent transfer amounts or by reversing transfers.

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

Reverse transfers

Connect supports the ability to reverse transfers made to connected accounts, either entirely or partially (by setting an amount value). Use transfer reversals only for refunds or disputes related to the charge, or to correct errors in the transfer.

Command Line
cURL
curl https://api.stripe.com/v1/transfers/
{{TRANSFER_ID}}
/reversals
\ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=7000

Transfer reversals add the specified (or entire) amount back to the platform’s available balance, reducing the connected account’s available balance accordingly. It is only possible to reverse a transfer if the connected account’s available balance is greater than the reversal amount or has connected reserves enabled.

If the transfer reversal requires a currency conversion, and the reversal amount would result in a zero balance after the conversion, it returns an error.

Disabling refunds for a connected account won’t block the ability to process transfer reversals.

See also

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