Splitting the check: Architecting multiparty payment flows
Advancing developer craft
Runtime
Vul het formulier in om de hele video te kijken
Marketplaces, platforms, and multiparty products share common architectural challenges. This session covers how to build payment flows that split funds among recipients, route transfers based on custom logic, and scale across geographies.
Speakers
Javier de Pedro López, Solutions Architect, Stripe
JAVIER DE PEDRO LÓPEZ: Hello everyone. Thanks for coming to this talk, “Architecting multiparty payment flows.” And my name is Javier, and I’m a solutions architect at Stripe. If this talk is successful, today you will get four key framework areas so you can think about how to architect those multiparty payment flows. But to do this, I want you to have a quick of an example. There is a company called, a fictional company called CreatorCamp. They sell courses that they create and their customers subscribe to those courses for $20 and consume them. Kind of like a boring, simple use case. But things have to evolve, and the company wants to grow a little bit more. So the business team decided that they wanted to become a marketplace and offer their own software to other creators who want to join the company. So how [will] they work? Well, they said from the $20, we can get 15% for us as a CreatorCamp company, and the rest we can give it to the creators.
Seems fine, right? They gave those requirements to the developer team and they said, “Well, we can do this in around two days.” They shipped it, and it exploded. It was really nice. Everyone was starting to use it. But things started to be a little bit problematic because they just divided the amount without thinking a lot of different things that you have to keep in mind when you are building a marketplace. So things like the payouts were failing, the FX cost was piling. And if you know more or less this, FX is the exchange conversion between two currencies and that eating their margin. And then the owner of the funds sometimes wasn’t really clear who was the owner of those funds. And regarding the files and reconciliation, they have like a 500 megabyte Excel file that they weren’t able to reconcile.
And things failed not because of the load, not because of the bags. It failed because they didn’t ask four key questions. The first one: how your business is and how is it evolving with the new requirements? Second one: who is legally responsible for the funds and in case of refunds or disputes, who actually has to take care of it? Regarding the FX, what’s the FX of the transactions, how those are implying my business, and how these FX will aid my margins? And finally, where the money sits—where is it guardrailed so I can distribute it? So today with the framework that I will give you, you will be able to answer all these questions in the right way and make the right decisions.
Let’s get started with the first one. And it’s, what is my business actually? What am I building? Remember, CreatorCamp was just a simple business with a subscription. Certainly they evolved to a marketplace and they didn’t think about it. You have to take care of FX, you have to take care of the KYC, liability, lots of different things. Typically, we’re talking about Connect when it comes to model this into Stripe. And so in Connect, there are certain things that you need to keep in mind depending on the kind of business that you’re building. If you’re a SaaS platform like is the case of Shopify, Mindbody, usually the merchant of record. And just quick pause here, the merchant of record is in the names of Stripe, who is in the descriptor, like who figures in the descriptor of the transaction, but also it implies where the money is landing and what payment methods are available.
So the merchant of record will be the connected account, the seller, the creator in this case. Typically, also they are offering software and services and they monetize through application fees and external subscriptions as well. But if you are building a marketplace, that’s a little bit different. The front of the marketplace usually is the brand of the marketplace itself. So the way they earn money is by demand generation. They get a lot of customers and take a little bit of a fee for every transaction, getting that way the right amount of money for their business.
And I’m saying that we are modeling this with Connect. In the Connect world, the platform is CreatorCamp and the connected accounts are the connected accounts. Sorry, the creators. So there, what happens is what we are modeling here is how these roles interact together between the platform and the connected accounts and all the hidden trades that are in this business model. First lesson I want you to take away from here is that the fund flows are your business model. They represent exactly what your business model is. So it’s not just a minor detail regarding how money moves in your last mile.
Okay. Let’s go to the second part. And this is all about the traits. If you’re familiar with Connect, they used to have types like Standard, Express, Custom. We changed this a little bit like past year. And what you have now is traits. Those traits are the Dashboard. So what is the UI, the representation that you are giving to your connected accounts and the experience they have, which can be full, express, or none, depending on how white-label you want to build. The second one is the economic model, which determines who is paying the fees to Stripe. And the third one, and this is the one I want you to pay a special attention, is merchant risk management represented by the <code class="InlineCode">losses_collector</code> property in the API. And here it could be Stripe or application. And this is important because liability gates the fund flows that you can currently use. If you use Stripe, that’s Stripe Managed Risk. You’re delegating your risk transaction for the transaction risk, and you’re delegating also the merchant risk. If you choose to use application, you’re getting all that responsibility. And that means also designing refunds and dispute fund flows.
So here is the second lesson I want you to take away today: liability gates your fund flows. So pick it first, not last. Okay. Let’s go to the third part of the framework. It’s geography. When you’re building a company, usually you have an actual business in one place, but sometimes you have to expand to other places and have it there. So when it comes to a marketplace or a platform, you need to decide how the money moves and where it lands, depending on how your business is. Option A, you have a company with a single place that sells globally. That’s fine, but you have another option, which is having one company in every place and creating a marketplace or a platform in that specific place. Choosing one or another, it really depends on the needs that you have as a business, and it all comes down to operational cost and transaction cost.
If you have many places where you’re operating, you have extra operating costs at the cost of transaction. While, if you are selling globally, you may have more FX cost and more cross-border cost. So you have to choose wisely. In the case of CreatorCamp, they were starting, so they have just a single platform in the US in this case, and they are selling everything globally.
So here’s the third lesson for today: architecture constrains your geographic scale. It’s not that there is no demand in those places, but it’s just the architecture wasn’t designed to be able to consume those demands. So make sure that whatever you are designing, you are creating those right fund flows, or at least you make them extensible.
Okay. So with these three things, we are able already to create our connected accounts. And the connected accounts, nowadays, we recommend to create them in the v2 APIs. This is our v2 API, but especially I want you to pay attention to the default responsibilities, which define exactly the traits that we saw before. This is what CreatorCamp selected. So <code class="InlineCode">fees_collector</code>, the platform, liability on Stripe, and the Dashboard being “none.”
And now we want to onboard those connected accounts. So to do so, we have to use two APIs. The first one is the account sessions, which unlocks a client secret that you can pass to the frontend to build your components. And because they wanted to have a white-labeled experience, what they did is create an onboarding component and passing the client secret to that onboarding component to allow people to onboard. The great thing about those components is that we take care of updating them. So whenever the regulation changes, we will take care of the KYC screening, the AML, and everything that is required to make sure that it’s compliant.
And now we come to the actual fund flows. That’s what we try to understand in here. So those arrows and how they interact between each other is what the fund flows are about. To do that, we’re going to have four different requirements, and we will evolve those requirements so you can fully understand when it makes sense to pick one or another.
So let’s get started with direct charges. Direct charges make the platform, sorry, the connected account the merchant of record. But let’s understand what are the requirements that right now they have. They have a single transaction, they have a seller, so the buyer wants to pay directly for a course. In these kinds of situations, it’s just the money landing directly to this connected account, so the creator.
So let’s just see how the fund flow goes. We have a PaymentIntent with $100, which goes directly to the connected account. It creates a charge and it goes to the balance. But now the platform wants to keep that application fee that we mentioned before. So for that, a platform fee is created and it’s transferred to the platform straight away. In this case, because the application is paying the fees, the fees from Stripe are charged to the platform directly.
How does it work on the API? Well, it’s just a payment intent API. You already know this. So you have a Stripe account header and you have the <code class="InlineCode">application_fee_amount</code>. Those two things are defined in it. But keep in mind one thing. The Stripe account, when you are using that header, every object is creating the connected account. So payment methods, customers, the payment itself is created on the connected account. And because the connected account is the merchant of record, we’re using the currency of the connected account, and also we’re landing those fees and, sorry, those charges on the connected account. Use these direct charges as the easiest payment flow that you can have. And many of the companies still remain in this kind of flow for years, and it’s perfectly fine. But if you start evolving your requirements, you may do some changes.
So here it goes. The business team just comes here and decides that they want to have a little bit more touch with the end customer, right? So to do that, they want to offer a buyer protection to those end customers. That buyer protection means that the losses_collector liability might be more on the platform and not on the connected account. So that’s the first change we have to do. But in terms of the fund flow itself, it’s called destination charge.
Let’s look how the money moves. So in this case, in destination charges, the platform is the merchant of record of the transaction. And the $100 move to the platform, creates the fee that is charged from Stripe, and at the end, leaves the balance. After that, we have a transfer directly and atomically, and this is very important to the connected account. Destination charges is an atomic operation. It defines where the charge is created and where it lands later on. Once the transfer is done, we create a platform fee and this automatically goes to the platform given exactly the amount that the platform is earning. API-wise, it’s pretty much the same as we had before, but without the Stripe account header. We’re adding also the <code class="InlineCode">transfer_data_[destination]</code>, which defines what is the connected account where money is landing. Use this one when you want to control the relationship of the transactions while only having a single receptor for the transaction itself. So it’s of course still a buyer sending the money to a connected account.
Okay. As this keeps evolving and it gets a lot of traction, people from different places in the world want to start using the platform. So in this case, someone from Germany who operates in euros has 8,000 subscribers that they want to onboard, but they are not accepting the FX that can happen in this. And with destination charges, this is the flow exactly that we saw before. If you present in dollars and the customer pays in dollars, there might be two FX, one for the payment on the presentment currency, and one for the transfer. There are two ways to resolve this. One is called multicurrency settlement, which basically allows the platform to have multiple balances in different currencies. In this case, they could have one in euro and one in dollar. But here, we want to touch on another option that we have, and it’s called <code class="InlineCode">on_behalf_of</code>.
So in this kind of fund flows, what happens is we’re making the connected account the merchant of record. And remember, being the merchant of record means as well that the funds are landing in the country of the merchant of record. So in this case, funds are being a straightaway landing in Germany. That means that we can charge euros and send euros or have euros in Germany straight away. The only gotcha here is the platform will have an application fee as well in euros. So they may end up with two balances, one in dollars and one in euros. In this case, what can the platform do? They can have two bank accounts, one in dollar, one in euros, and get them paid out. And if they don’t want to have a bank account in euros, they can also convert and send to their bank account straight away. You can use this whenever it makes sense the connected account is the merchant of record.
API-wise, if you looked at the destination charge API, it’s pretty much the same, but we are adding the on_behalf_of property into the transaction. One gotcha here. You can’t have an on_behalf_of for one connected account while having the transfer destination to another connected account, because that will be a little bit strange, making someone liable for the transaction while at the same time the money is landing somewhere else, and we don’t allow it.
Okay. Let’s go to the fourth requirement that came to CreatorCamp. And this is, we want to create bundles. We want to be able to sell things that come from multiple sellers. So in this case, a bundle could be a masterclass from Creator One or Creator A. It could be a tutorial series from Creator C. Or even we can have a brush pack that needs to be uploaded and created directly from Creator B.
In this case, there might be a delay between the time you do the transaction and the time you want to transfer to that connected account. So we can’t use all the charges that we saw before. The way to do this is separate charges and transfers. So if you look this, it’s similar to what happened with destination charges. Money lands directly on the platform and the merchant of record is the platform, but there is no transfer. This is not an atomic operation as it used to happen in destination charges. It needs to be scheduled. So the way you do this is by executing a call to the v1 transfer API, which will transfer the amount exactly that you want to transfer to the connected account. But keep in mind here, there are no application fees as it is. All the money that is landing is exactly what you sent. So that part of the ledger is lost.
API-wise is a little bit different. We are still using the PaymentIntent, but we are adding a transfer group property called a <code class="InlineCode">transfer_group</code>, and you can pass there a name, for example, the order ID. And at the same time, we’re creating two other API calls, one for one transfer and one for another, depending on when you want to execute those transfers, the responsibility to do those executions is on your side. So at the end, you have to decide when that happens based on your own business rules. Use this whenever you have multiseller or delayed transactions.
Okay, but we came here to see a little bit of architecture. So when you are on separate charge and transfer, things get a little bit more complicated, so this is how it looks like. Come with me and follow all the funds flows all along. Starting with a client application. The client application usually will call an API gateway, which will help you with the onboarding in one of the services, but also you may want to do a payment. So let’s see how the payment actually gets routed all around. When you have a payment service that creates a PaymentIntent, should be and has to be confirmed by the client application with your user. So the customer will click on the application to do the payment, and money will land at the end on Stripe, but it doesn’t end up here. We said that we have to transfer the funds to all the connected accounts depending on the different business rules.
So, how we do this? We listen to webhooks. Whenever the payment result is correct and we are happy with it, this will go to a webhook service that will listen for those payments. And at the end, probably we want to notify different services, especially if we are in a multiservice architecture. So we may want to have a message broker. That message broker will send those messages to each of the different services. So for example, we want to update the card order, or we want to annotate the result in a ledger, and this is important. In these kind of situations, you probably want to have your own ledger.
And third, maybe we want to grant access for a specific course that someone paid, but this isn't it. Remember, we have another person that needs to get paid out at the end, whenever the time is right. So we probably also need a cron scheduler that listens for different needs and sends the transfer with a transfer broker to the platform. So Stripe at the end does the transfer whenever it’s needed.
So here’s the fourth lesson for today: all the incidents that you may have in this kind of fund flows live in the edges, not in the boxes. The boxes is something that typically you engineer, the message broker, the transfer schedule, all of that are typical services that you have to build whenever you are handling payments. But when it comes to the actual incidents, it’s on all these little arrows that happen between the communication with each of the services where all your business is actually baked in.
Okay. So we’ve talked about all these different fund flows, but this is the diagram I want you to have in your wall whenever you are designing. If you are okay to get the losses collector, you can go on the right side. But if you want the losses collector and all the liability on Stripe, you have to make sure that you’re using direct charges.
Let’s say that’s not the case. Think about if you have delayed payments or you have a multiparty transaction. If that’s the case, separate charges and transfer is your actual fund flow. If that’s not the case, it’s destination charges. And whether you use on behalf of or not will depend if it makes sense for your business and your need to use destination on behalf of and the merchant of record being the connected account.
Okay. Let’s do a quick summary of what we’ve seen today in the framework. First one, the fund flows are your business model. Whenever your business model changes, like it’s the case of CreatorCamp, they build a normal business and then they move to a marketplace and they have to close it for three months just because they didn’t adapt their business to the actual change. Second one, pick your liability first, not last. It gates your fund flow. So whenever you are designing the fund flow, think about what is the appetite, what is the risk? Do you have the actual teams to take care of it? If that’s not the case, leave it to Stripe. Third, the architecture constrains your geographic scale. Usually whenever you are opening a new country, you know there is demand. There are customers there to pay, but if your business is not adapted and all the fund flows are not adapted to that, then it’s very likely that it breaks. And fourth, incidents live in the edges, not the boxes. Take time designing those edge cases that happen between the different systems and not as much designing the system itself.
So whenever you are designing your next marketplace platform, SaaS platform, whatever it is, just follow these rules, follow these four traits, and you will make better decisions. Don’t make this after your Excel file, make it before your PaymentIntent. Thank you so much. And if you need any questions, you have any questions, I will be in the Money Management booth, so happy to answer all of them. Thank you.