Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] A way to save a payment method on the main Stripe account when using Stripe Connect with direct charges to other account #4536

Open
Ariandr opened this issue Feb 4, 2025 · 17 comments
Labels
kind:improvement triaged Issue has been reviewed by Stripe and is being tracked internally

Comments

@Ariandr
Copy link

Ariandr commented Feb 4, 2025

Is your feature request related to a problem? Please describe.

The current problem we are facing is that we use Stripe Connect and we share payment methods during the payment itself, so the payment intent is configured using the connected account customer id right away. Therefore, setup_future_usage: off_session || on_session on Payment Intent options doesn't save the payment method to the customer on our main Stripe platform. (Wasn't an issue with Basic Integration)

It makes it impossible to use PaymetSheet.FlowController to save payment methods during payments and confirming the payment intent, because the intent isn't attached to our main Stripe platform account.

Image

Image

More context here: #4195 (comment)

Describe the solution you'd like

I see a couple of solutions:

  1. Implement a way to automatically save payment methods in PaymentSheet as it was in Basic Integration, where it worked flawlessly (discussed here Payment Information Entered in PaymentSheet.FlowController Does Not Appear as Saved Payment Method for the Customer #4315)
  2. Add ability to launch a PaymentSheet confirmation flow with the selected payment option from CustomerSheet and just provide the amount of money, currency, etc. So, the user can add/edit/select a payment method in CustomerSheet and then we launch a payment flow using PaymentSheet supplied with the pre-selected payment method.

Describe alternatives you've considered

We don't know how to finish this migration from Basic Integration to Payment Element. It seems Payment Element is missing a lot of functionality to use with Stripe Connect direct payments and it's really hard to deal with these use cases.

Can you suggest any workaround, so that we can use a CustomerSheet to select a payment method and then launch payment confirmation?

Additional context

For more context you can check these issues:

  1. ERROR An error occurred in PaymentSheet.There is a mismatch between the payment method ID on your Intent: pm_XXX and the payment method passed into the confirmHandler: pm_YYY #4195
  2. Payment Information Entered in PaymentSheet.FlowController Does Not Appear as Saved Payment Method for the Customer #4315
  3. [Question] Calling self.intentCreationCallback?(.success(clientSecret)) after intent was confirmed on the server causes error #4531

Now we made payments work, but we cannot save payment methods to the customers during the payment process itself. We can save them only when we use a CustomerSheet with setup intents, but it's irrelevant during the payment process. Now it's a remaining blocker to migrate from Basic Integration to the new Payment Element.

@Ariandr
Copy link
Author

Ariandr commented Feb 4, 2025

Hello @yuki-stripe
Please let me know if you do have a workaround for this case or a way to launch the payment confirmation flow with just using Customer Sheet and its selected payment option.

@seanzhang-stripe
Copy link

seanzhang-stripe commented Feb 5, 2025

Hi @Ariandr I believe this flow is already supported. Can you try the following?

  1. Initialize the Stripe iOS SDK without setting STPAPIClient.shared.stripeAccount, so that you can save a payment method on the platform by using a setup mode PaymentSheet or CustomerSheet
  2. Create a connect request from your backend to clone the payment method to the desired connected account. Remember to attach the cloned payment method to a customer in the connected account so that it can be reused
  3. Now you can set STPAPIClient.shared.stripeAccount in your iOS app, with the correct customerId, customerEphemeralKeySecret and paymentIntentClientSecret, your customer will see the clone payment method appearing in the PaymentSheet, and use it for payment.

The main objective for CustomerSheet is to let your customer manage payment methods (e.g., add/delete payment methods), and you should use PaymentSheet for payments.

It sound like you want your customer choose a payment method first before creating a PaymentIntent. If that's the case, you can follow the deferred intention creation guide to build your app.

@Ariandr
Copy link
Author

Ariandr commented Feb 5, 2025

Hi @seanzhang-stripe
Yes, we already use the deferred intent creation. We used it for more than 6 years on the project with Basic Integration and thousands of users perform payments every day.

Steps 1, 2, 3 are implemented and it works. Customer Sheet is used to attach payment methods and then they are visible in the Payment Sheet.

Overall, payments work with the new PaymentElement integration. But we cannot release it, because adding a new payment method to PaymentSheet.FlowController during payment process itself doesn't save it to the main Stripe platform.

The issue is next:
When we use the PaymentSheet during the payment process and the user adds a new card into Payment Sheet on the fly, there is no way for us to save this payment method to the main Stripe platform. Setting setup_future_usage: off_session || on_session during the server intent creation & confirmation (as is written in docs) doesn't save the payment method to the main Stripe platform, because the intent is intended for the connected account and the shared customer on that connected account.

So it makes it impossible to use PaymentSheet and let users add and save payment methods there, as it was in Basic Integration. When we show PaymentSheet STPAPIClient.shared.stripeAccount is set to nil, so it fetches payment methods from the main Stripe platform.

On this image below you can see that the Payment intent is created for a direct charge to the Connected account (sharedStripePaymentMethod and sharedStripeCustomer are shared from the main platform to the Connected account). We share the payment method and it works. But it doesn't save it on our main Stripe platform, which is a deal breaker for us.

Image

@seanzhang-stripe
Copy link

Hi @Ariandr Can you share with me the platform PaymentMethod ID that your Basic Integration saved through a Direct Charge created on connected account?

@Ariandr
Copy link
Author

Ariandr commented Feb 5, 2025

@seanzhang-stripe
When we use Basic Integration and add a payment method in built in UI before confirming a payment, it saves the new payment method to the customer. So it's saved even before any Payment Intent is created or confirmed on the server. So Basic Integration combines both Customer Sheet + Payment Sheet, speaking in these terms.

But sure, I can share it with you. It was created in the UI shared below and then used on the server for sharing and paying via Connect. It's the test environment. It's the ID on our main platform. I cannot provide the shared PM ID, I think we discard them and don't store on the connected accounts.
Payment Method ID: pm_1Qp9wjLulyQ5l0kK1jsFqK8S

Image

@yuki-stripe
Copy link
Collaborator

Hey @Ariandr, very sorry you're running into more issues. Let me see if I understand:

  1. Customer uses a saved card or adds a new card in FlowController, using your platform Customer
  2. Customer attempts to pay
  3. FlowController gives you the PaymentMethod id. This PM belongs to the platform.
  4. You send the PM id to your backend
  5. You clone the PM to your connected account, confirm the PI on your connected account, etc.

Would it work if, between step 4 and 5, you manually attached the PM to your platform's Customer? You'd use this API method. Looks something like

const paymentMethod = await stripe.paymentMethods.attach(
  'pm_1MqM05LkdIwHu7ixlDxxO6Mc', // PM id returned by FlowController
  {
    customer: 'cus_NbZ8Ki3f322LNn', // Your platform Customer id
  }
);

I know this isn't the same behavior as Basic Integration, which would save the card immediately after the customer completes the form, rather than at the end of checkout - we are working on adding this to PaymentSheet/FlowController this year, but no firm timeline yet.

@Ariandr
Copy link
Author

Ariandr commented Feb 5, 2025

Hi @yuki-stripe
Yes, you understand it correctly.
We saw this documentation regarding attaching the PM to the customer and we were considering it, but this specific phrasing put us off from using it.

Image

But probably it's a good workaround for now, thanks.

It's good to know that you are working on the ability to save the card immediately in PaymentSheet/FlowController.

@yuki-stripe
Copy link
Collaborator

Fair point. Basic Integration uses this attach API under the hood, so I'm not super concerned in your case if it was working for you before. I'll close this issue but please re-open if you do run into further issues!

@Ariandr
Copy link
Author

Ariandr commented Feb 6, 2025

Hi @yuki-stripe
Okay, thank you.

When you release a feature to attach payment methods directly from Payment Sheet, please mention it in release notes, so that we can switch to it.

@Ariandr
Copy link
Author

Ariandr commented Feb 27, 2025

Hi @yuki-stripe

A question to you.
As suggested, we tried to use this approach you suggested.

const paymentMethod = await stripe.paymentMethods.attach(
  'pm_1MqM05LkdIwHu7ixlDxxO6Mc', // PM id returned by FlowController
  {
    customer: 'cus_NbZ8Ki3f322LNn', // Your platform Customer id
  }
);

But we get the error This PaymentMethod was previously used without being attached to a Customer or was detached from a Customer, and may not be used again.

But if I add the same card in Basic Integration during payment or through Setup intent using Customer Sheet, it works.

Can you suggest any workarounds for this case?

#4536 (comment)

@yuki-stripe
Copy link
Collaborator

@Ariandr Can you provide an example payment method ID I can look up?

@yuki-stripe yuki-stripe reopened this Feb 27, 2025
@Ariandr
Copy link
Author

Ariandr commented Feb 27, 2025

@yuki-stripe
Yep, here it is. It's a test 4242 ... card.
pm_1Qx5uNLulyQ5l0kKR1nCqY1F

@yuki-stripe
Copy link
Collaborator

@Ariandr Thank you! Based on logs I believe I'm seeing you first clone the PM and then attach it. Can you try attaching it before cloning?

@Ariandr
Copy link
Author

Ariandr commented Feb 27, 2025

@yuki-stripe
Yep, will do, thanks. I'll let you know if it helps.

@Ariandr
Copy link
Author

Ariandr commented Feb 28, 2025

Hi @yuki-stripe
Yes, it's working, the attachment process, thanks.
But the next step is to make it a default payment method for the customer, so that it works just like Basic Integration. Now it doesn't change the default payment method for some reason.

Do you happen to have an idea why it doesn't work? Or should it be done differently?

__attachPaymentMethodToCustomer: async function (stripeCustomerId, paymentMethodId, logger) {
    try {
      await stripe.paymentMethods.attach(paymentMethodId, {
        customer: stripeCustomerId,
      });

      const updatedCustomer = await stripe.customers.update(stripeCustomerId, {
        invoice_settings: {default_payment_method: paymentMethodId},
      });

      return updatedCustomer;
    } catch (error) {
      logger.info('Attach payment method failed', stripeCustomerId, paymentMethodId);
    }
  },

@yuki-stripe
Copy link
Collaborator

@Ariandr Unfortunately, there is no proper way to achieve this today. As a hacky and brittle workaround, you can do this:

@_spi(STP) import StripePaymentSheet

CustomerPaymentOption.setDefaultPaymentMethod(.stripeId("the payment method id"), forCustomer: "your stripe customer id")

@_spi(STP) lets you access private APIs in our SDK. Similar to accessing private Apple APIs, they may change or break in any version! So please check that it still works whenever you update the Stripe SDK.

I'll bump this thread again when we support all of this properly (save the card immediately, when you hit "Continue" in FlowController), and hopefully you can migrate to that and get rid of the hacks.

@Ariandr
Copy link
Author

Ariandr commented Mar 3, 2025

Hi @yuki-stripe
Okay, great, thank you for your support.

@yuki-stripe yuki-stripe added the triaged Issue has been reviewed by Stripe and is being tracked internally label Mar 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:improvement triaged Issue has been reviewed by Stripe and is being tracked internally
Projects
None yet
Development

No branches or pull requests

3 participants