stripe-ios icon indicating copy to clipboard operation
stripe-ios copied to clipboard

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

Open Ariandr opened this issue 1 year ago • 6 comments

Summary

We are migrating from Basic Integration to new Payment Sheet, using iOS and backend with payment intents. We use Stripe connect where we save the payment methods on the main platform account and then share these payment methods to make the Direct payments to Connected accounts.

With the Basic Integration (and with the PaymentSheet August version of SDK) it worked perfectly for us, but with the new Payment Sheet releases (after https://github.com/stripe/stripe-ios/pull/3972) there is an issue that we cannot confirm it on the iOS side later due to a problem that the initial payment method doesn't match the one inside the payment intent.

https://docs.stripe.com/connect/direct-charges-multiple-accounts

iOS version

iOS 18, but not related to iOS version

Installation method

Cocoapods

SDK version

23.32

Other information

Essentially, our flow it this:

  1. Use the PaymentSheet.FlowController to select a payment method
  2. Send the request to the backend with the selected paymentMethod.stripeId
  3. Backend shares this payment method to a connected account and puts the shared one inside the payment intent. Then, the backend tries to confirm the payment intent.
  4. Then backend sends the payment intent secret back to iOS
  5. On iOS, we are tying to call intentCreationCallback?(.success(clientSecret))
  6. If the intent requires next action (e.g. 3DSecure), we get this 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

With Basic Integration and older PaymentSheet versions this action is handled without issues and we confirm the payment with 3DSecure. But with the new PaymentSheet releases this got broken.

I saw you added this check in this pull request, but it breaks our use case unfortunately. https://github.com/stripe/stripe-ios/pull/3972

It works with the older (August) version of SDK, e.g. 23.29.2. Can we add a way (maybe additional configuration option) to omit this check, so our use case is not broken?

Ariandr avatar Oct 28 '24 19:10 Ariandr

Hello @Ariandr, sorry you're running into this. How are you "sharing" the payment method? In the integration you linked (https://docs.stripe.com/connect/direct-charges-multiple-accounts), cloning happens after the confirmation succeeds and you shouldn't run into this issue.

yuki-stripe avatar Oct 28 '24 21:10 yuki-stripe

@yuki-stripe To make it simpler, probably the steps will be easier:

  1. We choose a payment method using PaymentSheet.FlowController using the Main account stripeId on iOS/Android and pay.
  2. We send payment method id to the server to finalize the payment.
  3. On backend (NodeJS)
// 1. Create a shared payment method for the connected account
let sharedStripePaymentMethod = await stripe.paymentMethods.create(
      {
        customer: stripeCustomerId,
        payment_method: paymentMethodId,
      },
      {
        stripeAccount: clubStripeAccountId,
      },
    );
    
    ......
    
// 2. Use it to create and confirm a payment intent
let intentOptions = {
      payment_method: sharedStripePaymentMethod.id,
      customer: sharedStripeCustomer.id,

      amount: updatedAmount,
      currency: currency,

      return_url: return_url,
      confirmation_method: 'manual',
      use_stripe_sdk: true,
      save_payment_method: false,
      confirm: true,

      ....
    };
  1. Send the intent client secret back to the iOS/Android
  2. If it requires action (e.g. 3DSecure), we set a connected stripe account id (both in Basic and new Payment element integrations), then call intentCreationCallback(.success(clientSecret)). In the last version it gives the error (before showing 3DSecure authentication).
  3. After the next action is handled, we send a new request to the backend with the payment intent id to let it fully confirm the payment and assign the purchased service product to the user.

As you see, we finalize the payment manually on the backend side.

Using August SDK version it works.

In the docs, it also uses a cloned payment method (with a new unique id) to create and confirm the payment intent, so if the next action is required on iOS/Android, the app will have to go through 3DSecure after intentCreationCallback(.success(clientSecret)), since we are using manual confirmation and it cannot be confirmed before. image

Ariandr avatar Oct 28 '24 22:10 Ariandr

Side note. It would be great if the PaymentSheetResult provided the completed intent. For manual server-side confirmation it's pretty inconvenient to keep track of the intent to send it back to the server after the required client action has been performed on iOS.

PaymentSheetResult {
    case completed(intent: Intent)
    ....
}

Wherever makePaymentSheetResult is called in SDK there is access to the intent and this change won't break anything I believe.

I use this workaround instead:

var clientSecret: Sting? // Last received from the server

func confirmPayment() {
      self.paymentSheetFlowController?.confirm(from: self, completion: { paymentResult in
            switch paymentResult {
            case .completed:
                if let clientSecret = clientSecret {
                        STPAPIClient.shared.retrievePaymentIntent(withClientSecret: clientSecret) { paymentIntent, error in
                                guard let paymentIntent = paymentIntent else {
                                    return
                                }
                                
                                if paymentIntent.status == .requiresConfirmation {
                                     let paymentIntentId = paymentIntent.stripeId
                                     // Send paymentIntentId back to the server to finalize the payment
                                }
                            }
                            
                            ....
                            ....
}

Just a suggestion from me, it's not directly related to the issue, just that you can understand the use case🙂

Ariandr avatar Oct 28 '24 23:10 Ariandr

Hi @yuki-stripe Do you know any workarounds to skip this validation for our case? ERROR An error occurred in PaymentSheet.There is a mismatch between the payment method ID on your Intent

Because otherwise new versions of SDK become unusable for us :(

Ariandr avatar Oct 30 '24 10:10 Ariandr

Hey @Ariandr, I believe we'll indeed need to update the validation logic in https://github.com/stripe/stripe-ios/pull/3972 to accommodate your use case. Sorry for the delay here and sorry for missing this scenario when we added the check! We should be able to get the fix into the next SDK release this coming Monday.

yuki-stripe avatar Oct 30 '24 16:10 yuki-stripe

@yuki-stripe Great, thank you for the response and action. Appreciate it!

Ariandr avatar Oct 30 '24 17:10 Ariandr

I will close this issue for now, but haven't tested it yet, since we cannot switch immediately to SDK version without Basic Integration. In case there are issues, I will reopen it later.

Ariandr avatar Feb 06 '25 13:02 Ariandr