DEV Community

Cover image for Picking the right charge type for your Stripe Connect platform
CJ Avilla for Stripe

Posted on • Updated on • Originally published at cjav.dev

Picking the right charge type for your Stripe Connect platform

Stripe Connect is a set of APIs for routing payments between multiple parties. Connect can collect payments from end customers and send that money to connected accounts. There are several options for moving money, sometimes called funds flows or charge types. In this article, we'll explore the different options: Direct, Destination, and Separate Charges and Transfers (SCT).

Choosing the correct charge type for your integration can significantly impact user experience, responsibility for chargebacks and risk management, and reporting. We'll compare and contrast the different flows, highlighting their key features, use cases, and potential pitfalls. We'll also cover which connected account configurations work best with each funds flow. Whether you're a seasoned developer or new to Stripe Connect, this guide will give you the information you need to decide which suits you.

Background

We'll use the term "platform" or "platform account" to refer to the Stripe account you're using to build your platform or marketplace. The "connected account" is the Stripe Account of your user, the person or the business you are enabling. When a payment is collected from an end customer, that payment can only "settle" on one account, which is sometimes referred to as the merchant of record or the settlement account. The settlement account's details are the ones that appear on the end customer's statement. Money can also move between Stripe accounts via either Transfers or Application Fees. These mechanisms allow you to split payments between one or many recipients and to collect fees during the transaction.

Direct charges

Direct charges were introduced as the first Connect funds flow, and it's the easiest to reason about. Direct charges allow platforms to collect payments from end customers, with the payment going directly to a particular connected account.

Characteristics of direct charges:

  • Stored on the connected account - we say the connected account is the settlement merchant.
  • The connected account pays Stripe processing fees.
  • The connected account is responsible for handling disputes and managing refunds. The platform can help, but the end responsibility lies on the connected account.
  • Most compatible with accounts with access to the Standard dashboard.
  • Customers directly transact with your user, often unaware of your platform's existence.
  • A single connected account (your user) is involved in the transaction.
  • You, the platform, may be insulated from fraud and disputes, depending on who is responsible for losses on the connected account. For Standard, Stripe manages fraud and losses on the connected account.
  • Suitable for platforms that are infrastructure providers for other businesses (ex. Shopify or Squarespace)
  • Only supported for connect accounts with the card_payments capability

Direct charges are made by adding a Stripe-Account header to an API call when creating payment-related objects like PaymentIntents or Checkout Sessions. For example, one of the connected accounts for my platform has ID acct_1LuN62ClUWl50Go4. Here's an example API call to create a PaymentIntent for $20 USD using a direct charge that will flow into the connected account:

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d payment_method=pm_card_visa \
  -d confirm=true \
  -H "Stripe-Account: acct_1LuN62ClUWl50Go4"
Enter fullscreen mode Exit fullscreen mode

Because direct charges are distributed across connected accounts on your
platform, it can be challenging to query for and report on aggregate statistics
related to direct charges. As a platform owner, you have to go to each
connected account and see the direct charges made on that account.

There are several reasons why you should avoid using Direct charges with
Express or Custom account types:

Addressing disputes becomes increasingly tricky through the dashboard.

The Express dashboard does not support dispute management, so you, as the
platform, will need to handle disputes for Express accounts or build dispute
management in your own platform dashboard. This involves building complex state
management to collect evidence and submit it.

When you're starting out and only have a couple connected accounts, it's
relatively easy to check each account for the necessary charge and then address
a dispute. However, finding the correct charge and dispute becomes more
challenging as your business scales.

You, as the platform, have to cover negative balances of your Express and Custom accounts.

Refunds and disputes for Direct Charges come from the connected account's balance, and Stripe fees (which are paid by the connected account for Direct Charges) are not returned, making it more likely for a connected account to have a negative balance. You are responsible for your Express and Custom accounts' negative balances, so Stripe has to hold a reserve from your available balance to cover the negative balances across your connected accounts. You can read more about reserve balances in the documentation.

Direct charges require more work to manage Radar Rules across all connected accounts.

Your platform account Radar rules only apply to your charges, not charges
directly created on connected accounts. This is a problem because Direct
Charges use the connected account's rules, and Express and Custom accounts
cannot set their own Radar rules, so you, as the platform, would need to
configure the rules for every connected account.

Example use-cases for Direct charges:

  • An e-commerce platform like Shopify or Squarespace
  • An accounting platform that enables invoice payments like Freshbooks

Destination charges

The second Stripe Connect funds flow we'll discuss is destination charges. Destination charges allow platforms to create a charge and a transfer object with one API call. Instead of using the Stripe-Account header, you make an API call to create a payment-related object with the transfer_data.destination set to the connected account's ID. This creates an atomic transaction, where the charge and transfer are made at the same time.

This one API call will create the payment on the platform, then create a transfer moving $20 USD to the destination account.

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d payment_method=pm_card_visa \
  -d confirm=true \
  -d "transfer_data[destination]=acct_1LuN62ClUWl50Go4"
Enter fullscreen mode Exit fullscreen mode

Characteristics of destination charges:

  • The charge is on the platform account
  • Best funds flow for Express and Custom account types
  • Customers transact with your platform for products or services provided by your user
  • A single connected account is involved in the transaction
  • The platform can reverse the transfer independently of refunds/disputes on the charge
  • The platform has more control but is more involved in the flow of funds
  • Suitable for platforms that are a unified business
  • Uses the Stripe payment fee pricing of the platform
  • Allows the platform to share customers and their payment methods across connected accounts

Example use-cases for destination charges:

  • A ride-hailing service like Lyft
  • A services platform like Thumbtack
  • Platforms for creators
  • Tipping and affiliate marketing programs

In this example, you, the platform, still pay the Stripe processing fees. Still, the entire amount was sent to the connected account, so your platform is "out" the processing fees in this example. Stay tuned for the next article, where we'll talk about application fees and approaches to splitting the payment between your platform and the connected account so that you can take a cut.

Separate Charges and Transfers (SCT)

Separate charges and transfers were introduced to collect payment and transfer proceeds at different times. This funds flow is only supported if both your platform and the connected account are in the same region. For example, both in Europe or both in the U.S.

Using separate charges and transfers requires at least 2 API calls. One to create the payment-related object like a PaymentIntent or Checkout Session, then a second to create a Transfer to move money from the platform to one or more connected accounts. You can even create several Transfers to split a payment between several connected accounts.

First, we'll collect payment from the end customer to our platform (note that this does not use the Stripe-Account header):

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d payment_method=pm_card_visa \
  -d confirm=true \
  -d transfer_group=demo-01
Enter fullscreen mode Exit fullscreen mode

Then, we'll transfer part or all of those funds to the connected account:

curl https://api.stripe.com/v1/transfers \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d destination=acct_1LuN62ClUWl50Go4 \
  -d transfer_group=demo-01
Enter fullscreen mode Exit fullscreen mode

(If you're curious about the transfer_group parameter, stay tuned!)

Characteristics of SCT:

  • The charge is stored on the platform
  • The platform pays the Stripe processing fees
  • Express or Custom account types work best because it gives the platform more flexibility, for instance, when it comes to handling fraud and disputes
  • It should only be considered after ruling out destination charges

SCT is ideal in any of these instances:

  • Multiple connected accounts are involved in the transaction
  • A specific connected account isn't known at the time of charge
  • Transfer can't be made at the time of charge

Example use-cases for SCT:

  • An e-commerce marketplace that allows a single shopping cart for goods sold by multiple businesses
  • A delivery service, like Instacart, that needs to split a payment between a store (the source of the items being delivered) and a delivery person

Transfer group

Think carefully about the 2 API calls we just made. If we leave out the transfer_group parameter, there is no way to know which payment is related to the transfer. Another issue you'll encounter with those API calls is related to pending funds. Depending on the payment method used, funds could take a while to "settle" into an account. By default, you can only transfer funds that have been settled. So enter two new API parameters that we can use to address these issues: transfer_group and source_transaction.

The transfer_group parameter is used to associate charges with transfers. It is primarily used for reporting purposes, allowing you to query all transactions by transfer group through the API.

Let's update our API calls to include a transfer group:

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d payment_method=pm_card_visa \
  -d confirm=true \
  -d transfer_group=demo-01
Enter fullscreen mode Exit fullscreen mode
curl https://api.stripe.com/v1/transfers \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d destination=acct_1LuN62ClUWl50Go4 \
  -d transfer_group=demo-01
Enter fullscreen mode Exit fullscreen mode

⚠️ Note that for transfer groups, you must be configured to use manual payouts or a combination of automatic payouts and the source_transaction parameter to ensure the platform has sufficient funds to cover the transfer.

You can group multiple charges with a single transfer or multiple transfers with a single charge. You can also perform transfers and charges in any order.

Source transaction

The source transaction parameter helps transfer pending funds, allowing you, the platform, to make a PaymentIntent and immediately create a transfer, even if the charge is still pending.

You can use this parameter to transfer funds from a charge before they are
added to your available balance. A pending balance will transfer immediately
but the funds will not become available until the original charge becomes
available. See the Connect documentation for
details
.

curl https://api.stripe.com/v1/transfers \
  -u sk_test_abc123: \
  -d amount=2000 \
  -d currency=usd \
  -d destination=acct_1LuN62ClUWl50Go4 \
  -d transfer_group=demo-01 \
  -d source_transaction=ch_3MPoP2CZ6qsJgndJ1u1Uhi01
Enter fullscreen mode Exit fullscreen mode

Note that you'll need to pass the ID of the underlying Charge, not the ID of the PaymentIntent.

Conclusion

Stripe Connect provides a range of funds flows that you can use to route payments between multiple parties. Each has different use cases, advantages, and drawbacks. Direct charges are the easiest to implement but are limited to Standard account types and have difficulty with dispute management and reporting. Destination charges are ideal for Express and Custom account types and provide more control. Finally, separate Charges and Transfers (SCT) require at least two API calls and are best used when multiple connected accounts are involved in the transaction. I recommend weighing the advantages and disadvantages of each funds flow and deciding which is best for your integration. Also, be sure to account for reserves and use transfer_group and source_transaction where applicable.

The charge-type decision comes down to a few factors:

  • Who does the end customer think they are doing business with? I.e., who's the settlement merchant that will appear on the statement.
    • As a Lyft passenger, I expect to be doing business with Lyft and not the driver. (SCT or Destination)
    • As a customer buying shoes from a Shopify store, I expect to be doing business with the store. (Direct)
  • Will the payment need to be split among multiple recipient connected accounts? (SCT)
  • Who should pay the Stripe fees?

About the author

CJ Avilla

CJ Avilla (@cjav_dev) is a Developer Advocate at Stripe, a Ruby on Rails developer, and a YouTuber. He loves learning and teaching new programming languages and web frameworks. When he’s not at his computer, he’s spending time with his family or on a bike ride 🚲.

Oldest comments (0)