flutter-stripe / flutter_stripe

Flutter SDK for Stripe.
https://pub.dev/packages/flutter_stripe
957 stars 528 forks source link

[flutter_stripe_web] How to get 3DS working in Flutter Web? #677

Closed jenshor closed 2 years ago

jenshor commented 2 years ago

I opened this up mainly to figure out a workaround.

Is your feature request related to a problem? (please describe) I'd like to do 3DS verification for EU-related credit cards as described in Card authentication and 3D Secure.

Describe the solution you'd like I've never used Paymentsheet but i'm assuming that it would solve the problem.

Describe alternatives you've considered My setup looks like the following:

Additional context When i fetch the PaymentIntent via GET https://api.stripe.com/v1/payment_intents/:id I see that next_action is defined in the response data:

    "next_action": {
        "type": "use_stripe_sdk",
        "use_stripe_sdk": {
            "type": "three_d_secure_redirect",
            "stripe_js": "https://hooks.stripe.com/redirect/authenticate/src_1KoOqNAE890sk4pj6jl56n5B?client_secret=src_client_secret_npuWujSqs8cPQWKOuKEMRHMs&source_redirect_slug=test_YWNjdF8xS1pkWWVBRTg5MHNrNHBqLF9MVlBmT0dWTGc4ZUZoN00xVWFtY1FoWU8xaE13U0VS0100ywjlEIDE",
            "source": "src_1KoOqNAE890sk4pj6jl56n5B"
        }
    },

When I click the hooks.stripe.com link i get to the test version of the 3DS verification process. On completion, the PaymentIntent's state changes to Succeeded.

I talked to the Stripe customer support and they indicated that i must use the Stripe SDK. Unfortunately i'm not sure how to execute this.

The options I see are the following:

However, I'm unsure if I can just forward my user to this URL or use the iFrame approach since i'm afraid this might expose sensitive data.

Do you have any assessment on my proposed approaches?

remonh87 commented 2 years ago

yess we have a method for this you can call handleCardAction method. For example see this code snippen from no_webhook_payment_screen:

     if (paymentIntentResult['clientSecret'] != null &&
          paymentIntentResult['requiresAction'] == true) {
        // 4. if payment requires action calling handleCardAction
        final paymentIntent = await Stripe.instance
            .handleCardAction(paymentIntentResult['clientSecret']);

        if (paymentIntent.status == PaymentIntentsStatus.RequiresConfirmation) {
          // 5. Call API to confirm intent
          await confirmIntent(paymentIntent.id);
        } else {
          // Payment succeeded
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('Error: ${paymentIntentResult['error']}')));
        }
      }
jenshor commented 2 years ago

Thanks for the quick response 👍🏻 Your answer helped me understand whats going on, but i am facing additional problems.

I tried to use handleCardAction but the upcoming error indicated that i should use confirmPayment instead:

IntegrationError: handleCardAction: The PaymentIntent supplied does not require manual server-side confirmation. Please use confirmCardPayment instead to complete the payment.

However, confirmPayment throws an error as well:

Usage:

    await Stripe.instance.confirmPayment(
      clientSecret,
      const PaymentMethodParams.card(
        setupFutureUsage: PaymentIntentsFutureUsage.OffSession,
        billingDetails: BillingDetails(
          email: '...',
          address: Address(
            city: '...',
            country: '...',
            line1: '...',
            line2: '',
            postalCode: '...',
            state: '...',
          ),
          phone: '...',
          name: '...',
        ),
      ),
    );

Error:

IntegrationError: Invalid value for confirmCardPayment: payment_method.card should be an object or element. You specified: null.

I'm not aware that I passed a null value.

Let me know, if you need additional information.

jenshor commented 2 years ago

Ok the error described above occurs because I did not have a CardField present on the screen. I was able to solve the problem.

robmr88 commented 2 years ago

yess we have a method for this you can call handleCardAction method. For example see this code snippen from no_webhook_payment_screen:

     if (paymentIntentResult['clientSecret'] != null &&
          paymentIntentResult['requiresAction'] == true) {
        // 4. if payment requires action calling handleCardAction
        final paymentIntent = await Stripe.instance
            .handleCardAction(paymentIntentResult['clientSecret']);

        if (paymentIntent.status == PaymentIntentsStatus.RequiresConfirmation) {
          // 5. Call API to confirm intent
          await confirmIntent(paymentIntent.id);
        } else {
          // Payment succeeded
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('Error: ${paymentIntentResult['error']}')));
        }
      }

Hi, if I use HandleCardAction i get this error:

Uncaught (in promise) Error: IntegrationError: handleCardAction: The PaymentIntent supplied does not require manual server-side confirmation. Please use confirmCardPayment instead to complete the payment.

If I use confirmCardPayment the 3dSecure popup appears to complete the payment with the verification code through SMS or app code, depending on the system the card uses. But I am having another problem, I am trying to complete a payment with 3ds and the popup appears asking me to complete the payment with the sms code but I am not receiving the sms code. Am I doing something wrong?

jenshor commented 2 years ago

Hey @robmr88, I'm assuming you are using Flutter Web. I reconsidered using flutter_stripe because I found the billing_portal endpoint from the stripe API. With this you can configure precisely what the customer is subscribing to and from the response you can extract an URL (which is pretty similar to the one provided with payment links).

Based on the portal it is a lot easier to collect card details and 3DS is handled as well (but on the stripe site). For me it was sufficient enough to implement what I wanted, without even having to integrate any Stripe input within my app. I'm using this with firebase backend.

It might be sufficient for you as well. Otherwise - good luck with flutter_stripe 👍🏻

robmr88 commented 2 years ago

@JensHor , yes, I am using Flutter Web, I just checked your solution but I cannot apply it because we are in a situation in which we cannot/should not exit our website. So we went with Flutter Stripe because it offers native Flutter widgets like CardFiled that allow to process all the payment without having to exit the website and come back with return urls.

So i have to stick with it, the 3ds popup appears and it shows the right url to complete the payment, it's just that I'm not getting the sms, so I just have to figure out if I'm missing something

robmr88 commented 2 years ago

@remonh87, do you have any good advice on this? Would really appreciate

remonh87 commented 2 years ago

@robmr88 I would recommend contacting Stripe support because getting an sms is something with the platform as a whole and not related to this library as such.

adnan-nazir commented 2 years ago

Flutter widgets like CardFiled that allow to process all the payment without having to exit the website and come back with return urls.

Hi @remonh87, @robmr88 mentioned that Flutter Stripe plugin does not exit the website and no need to come back with return urls. Just want to clear that is it so?

For 3DS cards verification Stripe Displays a popup on the web and webview on the mobile. I have not provided the return_url but it's returning me back to My App/website. Question is: Do I need to provide return_url OR is there any chance it will exit the app/website and move to another app/website and will return to my app using return_urls?

robmr88 commented 2 years ago

Hi, I can confirm that you can make the 3DS check without exiting your app or website

fahadappguy commented 2 years ago

Thanks @robmr88.

shubham-jain2 commented 1 year ago

Ok the error described above occurs because I did not have a CardField present on the screen. I was able to solve the problem.

Hi @jenshor could you please explain how you solved your issue. Thank you