How to charge a customer with saved payment details (Stripe Connect)

If you’re building a marketplace on Bubble and using Stripe Connect for payments, the best way to get started processing payments is usually via a Stripe Checkout Session (or maybe a Payment Link).

However, things get a bit more complicated when you want to charge a customer using saved payment details and DON’T want them to have to complete a Stripe payment form again.

You might use this type of payment flow in the following scenarios:

  • Charge a customer as part of a backend workflow
  • As part of an eCommerce store, you want to save a customer’s payment details when they make their first purchase so you can implement one-click purchases on all future orders
  • Charge an initial deposit upfront and take the rest of the payment at a later date

Fortunately, you can do all this with Stripe Connect via their Payment Intents API. The rest of this post will outline the steps you can take to charge a user using saved payment details in your own Bubble app, using the Stripe Connect - Marketplace plugin. We’ll be using an eCommerce style marketplace app as an example, where different sellers can list their products for sale and your marketplace takes a % of each payment as a commission.

Demo app
You can view an example of how to implement payments using saved payment details in the demo app linked below (editor is public):

Overview of steps involved

  1. Structure your database
  2. Create a new ‘Stripe Customer’ when a user signs up for your app
  3. Allow customers to make their first payment via a Stripe Checkout Session
  4. Save down the customer’s payment method by using a Stripe webhook and a backend workflow
  5. Create and confirm a paymentIntent (i.e. process the payment) on subsequent transactions

Bonus/slightly more optional (but still recommended) steps

  1. Handle failed vs. successful payments
  2. Allow a user to delete a payment method

Note: This post assumes you have already set up your marketplace app so that you can onboard merchants and allow them to register for a Stripe account. If you haven’t got to that stage, I suggest watching this video tutorial.

1. Structure your database
There are a number of ways you can structure your database, but I would suggest something like the following at a bare minimum:

You can view the database section of the demo app if you want to see this in Bubble.

2. Create a new ‘Stripe Customer’ when a user signs up for your app
Whenever a user signs up for your app, create a new Customer object in Stripe. You can do this by using the ‘Stripe Connect - Create Customer’ action that comes with the Stripe Connect - Marketplace plugin. Save the Customer ID returned by this action to the user in your database.

You can see an example of this workflow in this page of the demo app.

You should now have a Customer ID saved down in your database for the new user:

3. Allow customers to make their first payment via a Stripe Checkout Session
Given your customers haven’t purchased anything yet, they will need to input their payment details the first time they make a payment. You can use a Stripe Checkout Session to process this first payment.

2024-10-10 14.46.12

If you haven’t set up Stripe Checkout Sessions for a marketplace app before, you can check out this video tutorial (31:17 to 47:44 is the relevant section). You can also see this page of the demo app for an example (the ‘Purchase’ button).

If you’ve set things up correctly, the payment should be split between your platform and the seller of the product.

4. Save down the customer’s payment method by using a Stripe webhook and a backend workflow

Note: It is highly recommended that you inform your users that you are saving down their payment details for future use and give them the option to opt out of this. Different countries have different rules and regulations on this, and you’ll want to make sure you’re following all the applicable rules.

In order to later create a paymentIntent, we need to save down the Stripe ID of the payment method the customer used in the initial Stripe Checkout Session. There are several ways you could do, but the most robust solution is to use a Stripe Webhook. Specifically, you want to use the ‘checkout.session.completed’ event to trigger a backend workflow in your Bubble app whenever a Checkout Session is completed.

If you’re unfamiliar with how to implement Stripe Webhooks, you can watch 23:31 to 29:05 of this video (it’s not specifically to do with Stripe Connect, but the idea is the same).

In the backend workflow, you’ll first want to save down the paymentIntent ID associated with the Checkout Session:

Then you can use the ‘Stripe Connect - Get Payment Details’ call to get the Payment Method ID:

If you’ve set all of this up correctly, the new customer in your database should look something this after going through the initial Checkout Session:

5. Create and confirm a paymentIntent (i.e. process the payment) on subsequent transactions
All the hard work is done! Next time your customer goes to purchase a product, you can use the ‘Stripe Connect - Create PaymentIntent’ action that comes with the Stripe Connect - Marketplace plugin to create and confirm a paymentIntent. In the below example, the marketplace is taking a 20% commission in the sale (as defined in the Platform Fee (Cents) field):

You can also easily save down all the data associated with this paymentIntent in the next step of the workflow:

If everything is set up correctly, you should see the payment come through on your Stripe dashboard, with both your marketplace and the seller splitting the total value.

Collected Fees

Resources and further reading

Disclaimer
There are a number of ways you can implement this type of payment flow in your Bubble app and this is just the way that we’ve found easiest/most useful. Nothing in this post constitutes legal advice and it is always recommended to your own research when it comes to processing payments.

Bonus step #1: Handling failed vs. successful payments
Sometimes a payment method may have been valid when the customer used it in in the initial Checkout Session, but may not be valid when subsequent payments occur (especially if there’s long gap between first vs. subsequent payments). Therefore, you may wish to build in some error handing to your app.

One way to do this is to use the ‘status’ value associated with the paymentIntent. If the paymentIntent goes through smoothly, the status returned will be ‘succeeded’. You can use this in a conditional workflow action to show a ‘Success popup’, like in the gif below:

2024-10-10 16.04.55

Here’s the workflow action used to show the popup (you can also see it in the editor here):

If the payment was not successful, you can use similar logic to let the user know why the payment field. The ‘Stripe Connect - Create PaymentIntent’ action returns an error message, which will provide a reason for any payment failure. In the below example, the card attached to the customer (as a payment method) is invalid:

2024-10-10 16.11.38

Here are the workflow actions behind the ‘failed payment’ popup. First, we’re displaying the data associated with the transaction in the popup. Each transaction has a field called ‘error message’ which we can reference in the popup:

Then, we’re only showing the failed payment popup if the status of the paymentIntent is not ‘succeeded’:

Again, you can see this workflow in action here and see the editor behind it here.

If you’re creating the payment intent in a backend workflow (maybe you scheduled it to occur sometime in the future), you could send an email notification to the customer to let them know their payment method failed. It can also be useful to notify yourself that a payment didn’t go through as expected.

Bonus step #2: Allow a user to delete a payment method
A lot of apps will have a customer dashboard where users can delete their payment method. You can build this into your Bubble app by and get something like this:

As you can see I’m showing the last 4 digits of the user’s credit card. We can retrieve these digits from Stripe by using the ‘Stripe Connect - Retrieve a Payment Method’ call that comes with the Stripe Connect - Marketplace plugin. I’ve included in the backend workflow that is triggered by a Stripe webhook (see step 4 above):

We can then use the ‘Stripe Connect - Detach a Payment a Method’ call to remove this Payment Method from the current user in Stripe:

and FINALLY, we can reset the Payment Method ID and the last 4 digits in our Bubble database:

3 Likes

Just released a video tutorial version of this guide - hope it’s useful :slight_smile:

Any questions just let me know.