flutter_stripe icon indicating copy to clipboard operation
flutter_stripe copied to clipboard

[stripe_checkout] Unrecognized feature: 'payment' when calling redirectToCheckout

Open kduvignau opened this issue 3 years ago • 13 comments
trafficstars

Describe the bug I'm trying to use stripe_checkout package on Android to handle subscription creation, as flutter_stripe does not seem to handle it.

When I call redirectToCheckout, I got a white screen and this error in my console : I/chromium(19985): [INFO:CONSOLE(1)] "Unrecognized feature: 'payment'.", source: https://js.stripe.com/v3/ (1)

Also, I don't know what to put in successUrl and cancelUrl

Expected behavior The webview should open correctly

Additional context NB : I don't use any webview package

@override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: SafeArea(
        child: TextButton(
          onPressed: () async {
            final callable =
                FirebaseFunctions.instance.httpsCallable('checkout');

            final session = await callable();

            if (session.data['success']) {
              await redirectToCheckout(
                context: context,
                sessionId: session.data['sessionId'],
                publishableKey: publicKey,
                successUrl: 'https://checkout.stripe.dev/success',
                canceledUrl: 'https://checkout.stripe.dev/cancel',
              );
            }
          },
          child: Text('Pay'),
        ),
      ),
    );
  }

The sessionId is generated from my backend, I use Firebase cloud function. Here is my cloud function that creates the session :

export const checkout = functions.https.onCall(async () => {
  try {
    const result = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      mode: "subscription",
      line_items: [
        {
          price: "some_price_id",
          quantity: 1,
        },
      ],
      success_url: "https://checkout.stripe.dev/success",
      cancel_url: "https://checkout.stripe.dev/cancel",
    });

    return { success: true, sessionId: result.id };
  } catch (error) {
    functions.logger.error(error);
    throw new functions.https.HttpsError("invalid-argument", "Error while checkout");
  }
});

To Reproduce

  • Generate a session Id with a backend
  • Create a StatelessWidget with the code above
  • Click on Pay, a white screen should appear

Smartphone / tablet

  • Device: Pixel 2
  • OS: Android 9.0 (API 28)
  • Package version: flutter_stripe: ^2.4.0 and stripe_checkout: ^0.1.2
  • Flutter version: 2.10.2

kduvignau avatar Apr 19 '22 20:04 kduvignau

@jonasbark I'm trying to use stripe_checkout in a mobile context (Android/iOS) and not in web

kduvignau avatar Apr 22 '22 15:04 kduvignau

Is this package still maintained? Should I post my problem somewhere else or should I use another one for my use case?

I would like to handle subscriptions with Stripe in iOS/Android. We offer a physical service so we don't have to use in-app subscriptions.

kduvignau avatar Apr 26 '22 14:04 kduvignau

@kduvignau we just had a big update of the current package so unfortunately the web package is a bit lacking behind (also because it is in beta. We hope to spend some time on it the coming days but we are a bit stretched at the moment. Hope your understanding and we will come back to your ticket

remonh87 avatar Apr 26 '22 15:04 remonh87

@remonh87 Thanks for the reply! I'll wait then.

Could you just confirm that in theory, I can use stripe_checkout on mobile to handle subscriptions?

kduvignau avatar Apr 28 '22 12:04 kduvignau

Yes, you can!

jonasbark avatar Apr 28 '22 13:04 jonasbark

I runned the example app provided in the repository and I have the same error :

I/chromium(19638): [INFO:CONSOLE(1)] "Unrecognized feature: 'payment'.", source: https://js.stripe.com/v3/ (1)

kduvignau avatar May 04 '22 23:05 kduvignau

I'm also getting this Unrecognized feature: 'payment' error in the redirectToCheckout method. The documentation for stripe_checkout is lacking. E.g. There is no link to the example (I think it is the example) for stripe_checkout, which is actually found in the flutter_stripe package.

I/chromium( 8313): [INFO:CONSOLE(1)] "Unrecognized feature: 'payment'.", source: https://js.stripe.com/v3/ (1)

Is this the implementation for stripe_checkout? If so, there is no mention of installing stripe_checkout_web.dart. I tried this in flutter app and it would not build because stripe_checkout_web doesn't exist. Again, no docs on this, if this is the example. Do I need to install other packages like found here in the example pubspec.yaml to get stripe_checkout to work on android/ios and web?

Currently, I only have stripe_checkout: ^0.1.2 in my pubspec.yaml as I'm testing in Android Studio emulator.

Do I need this import on my payment screen for web?

import 'platforms/stripe_checkout.dart'
    if (dart.library.js) 'platforms/stripe_checkout_web.dart';

Screenshot from example checkout on flutter_stripe repo.

image

Server code to create Stripe session id (Session id creates no problem and returns to flutter app):

app.post('/create-checkout-session/:debug?', async (req, res) => {
  let debug = req.params.debug === 'debug'
  debug = true

  try {
    const stripeSecretKey = await getStripeSecretKey()
    const stripe = new Stripe(stripeSecretKey, {
      apiVersion: stripeApiVersion,
    })

    const { amount, show, successUrl, canceledUrl } = req.body

    if (!amount || !show || !successUrl || !canceledUrl) {
      functions.logger.error('Missing data', {
        amount,
        show,
        successUrl,
        canceledUrl,
      })
      throw Error('Missing data')
    }

    try {
      const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: [
          {
            price_data: {
              currency: 'usd',
              product_data: {
                name: show,
              },
              unit_amount: amount,
            },
            quantity: 1,
          },
        ],
        mode: 'payment',
        success_url: successUrl,
        cancel_url: canceledUrl,
      })

      res.json({ id: session.id, payment_intent: session.payment_intent, url: session.url })
    } catch (e) {
      res.status(400).json({ error: { message: e.toString() } })
    }
  } catch (e) {
    if (debug) {
      functions.logger.error(e)
    }
    res.status(400).json({ error: { message: e.toString() } })
  }
})

Dart code

Future<String> _createCheckoutSession() async {
  String onRequestUri =
      'https://mydomain.cloudfunctions.net/create-checkout-session';

  /// If in debug mode add debug flag
  if (kDebugMode) {
    onRequestUri = '$onRequestUri/debug';
  }

  debugPrint(onRequestUri);

  final int amount = _convertTotalPriceFractionToCents();

  debugPrint(amount.toString());

  final Uri url = Uri.parse(onRequestUri);
  final double totalPriceDouble = _totalPrice();

  if (totalPriceDouble == 0) {
    throw CatException('Please check that you have selected a ticket for all '
        'of your selected seats.');
  }

  final response = await http.post(
    url,
    headers: {
      'Content-Type': 'application/json',
    },
    body: json.encode({
      'amount': amount,
      'show': 'A Play',
      'successUrl': 'https://mydomain.com/success',
      'canceledUrl': 'https://mydomain.com/cancel',
    }),
  );
  final Map<String, dynamic> bodyResponse = json.decode(response.body);

  debugPrint(bodyResponse.toString());

  if (!bodyResponse.containsKey('id')) {
    throw CatException('Could not obtain a session id for payment. Please '
        'try again.');
  }

  return bodyResponse['id'] as String;
}

Future<void> _getCheckout() async {
  try {
    final String sessionId = await _createCheckoutSession();
    final result = await redirectToCheckout(
      context: context,
      sessionId: sessionId,
      publishableKey:
          kDebugMode ? kStripePublishableTestKey : kStripePublishableKey,
      successUrl: 'https://mydomain.com/success',
      canceledUrl: 'https://mydomain.com/cancel',
    );

    if (mounted) {
      final text = result.when(
        success: () => 'Paid successfully',
        canceled: () => 'Checkout canceled',
        error: (e) => 'Error $e',
        redirected: () => 'Redirected successfully',
      );
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(text)),
      );
    }
  } catch (e) {
    debugPrint(e.toString());
    rethrow;
  }
}
flutter doctor -v
[✓] Flutter (Channel stable, 2.10.5, on Manjaro Linux 5.4.188-1-MANJARO, locale
    en_US.UTF-8)
    • Flutter version 2.10.5 at /opt/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 5464c5bac7 (3 weeks ago), 2022-04-18 09:55:37 -0700
    • Engine revision 57d3bac3dd
    • Dart version 2.16.2
    • DevTools version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0-rc2)
    • Android SDK at /home/nate/Android/Sdk
    • Platform android-31, build-tools 31.0.0-rc2
    • ANDROID_HOME = /opt/android-sdk
    • ANDROID_SDK_ROOT = /home/nate/Android/Sdk
    • Java binary at: /opt/android-studio/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • CHROME_EXECUTABLE = /usr/bin/google-chrome-stable

[✓] Android Studio (version 2021.1)
    • Android Studio at /opt/android-studio
    • Flutter plugin version 67.0.1
    • Dart plugin version 211.7817
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)

[✓] Connected device (2 available)
    • sdk gphone x86 64 arm64 (mobile) • emulator-5554 • android-x64    • Android 11 (API 30) (emulator)
    • Chrome (web)                     • chrome        • web-javascript • Google Chrome 101.0.4951.54

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

Zelfapp avatar May 11 '22 01:05 Zelfapp

Since stripe_checkout doesn't seem to be the priority for now, would you recommend to use flutter_stripe to handle subscriptions instead?

As we can't use a payment intent directly, I would like to use the following flow :

  • [server] Create a stripe customer
  • [server] Initiate a setup intent (not a payment intent)
  • [client] Use Stripe.instance.initPaymentSheet(paymentSheetParameters: SetupPaymentSheetParameters(...)) to display the payment sheet and collect the payment method
  • [server] Attach the payment method to the customer using the setup intent id
  • [client] UI to confirm the user wants to subscribe
  • [server] Create the subscription using the customer id only

Could you confirm this is the right way to handle subscriptions with flutter_stripe ?

kduvignau avatar May 19 '22 12:05 kduvignau

Hi @kduvignau, @remonh87 I had the same issue and found the issue. Problem is simply that the template injecting the stripe js script has to be hosted over https and not declared as a variable:

this const is what cause the issue:

const _htmlPage = '''
<!DOCTYPE html>
<html>
<script src="https://js.stripe.com/v3/"></script>
<head><title>Stripe Checkout</title></head>
<body>
</body>
</html>
''';

this page ha to be hosted over https

gbaccetta avatar May 26 '22 21:05 gbaccetta

Hi @gbaccetta , thanks for your answer! We don't inject HTML anywhere, I guess stripe_checkout does this for us, so that's not in our hands right? How can we apply your fix using stripe.checkout.sessions.create ?

kduvignau avatar May 27 '22 20:05 kduvignau

Hi @kduvignau yes, I’m talking about the thé stripe_checkout library. I forked it and changed the html code with the initial url of a page with the same html code (but hosted on my website) and now it all works like a charm

gbaccetta avatar May 27 '22 22:05 gbaccetta

If you download the script and inject it, it's work.

http.Client _httpClient = http.Client();
              _httpClient.get(Uri.parse("https://js.stripe.com/v3/")).then((value) {                
                _webViewController!.loadHtmlString(_htmlPage.replaceAll("[[INJECT_JS]]", value.body), baseUrl: _baseUrl);
              });

with _htmlPage content:

<!DOCTYPE html>
<html>
<script>[[INJECT_JS]]</script>
<head><title>Stripe Checkout</title></head>
<body>
</body>
</html>

Guillaume-Fortin avatar Jul 21 '22 15:07 Guillaume-Fortin

Thanks @NitrofCG and @gbaccetta, I managed to handle subscription without using stripe_checkout

kduvignau avatar Aug 02 '22 16:08 kduvignau

@kduvignau subscriptions in not the only use-case for wanting to use checkout, so the bug is still valid I guess.

@gbaccetta did you manage to open a pull-request with the fix? What are the steps I should follow?

EDIT: Ok I've found the issue and fixed it, linking the PR here. You may eventually fix the issue temporarly by using this commit to fetch flutter_stripe in your pubspec.yaml like this:

  stripe_checkout:
    git:
      url: https://github.com/flutter-stripe/flutter_stripe.git
      ref: 1532f3f9ab6b999324bacc3370f78d85cb1ce634
      path: packages/stripe_checkout

Please, check the code for this commit before to simply fetch it, since anyone can create a PR as I just did.

NOTE: I also added the support for connected accounts in another commit linked here since I needed this in my use-case, feel free to use it if you want

nerder avatar Aug 25 '22 22:08 nerder

Hey @jamesblasco, I think this can be closed as well at this point.

Thank you so much for reviewing and merging it 🙏🏼

nerder avatar Sep 15 '22 13:09 nerder

The error Unrecognized feature: 'payment' when calling redirectToCheckout stems from the fact that the Payment Request API isn't supported in Flutter's WebView. This API allows for seamless payment procedures on websites, but it isn't universally compatible, especially with certain WebView environments. A potential workaround is to employ an external browser through a system like 'url_launcher', and then use a deep link to redirect back to the app post-payment. This allows your app to utilize the API's capabilities indirectly.

This issue is related to https://github.com/flutter-stripe/flutter_stripe/issues/993 Google Pay and Apple Pay are not visible because the webview can't support them.

MatteoAntolini avatar May 22 '23 21:05 MatteoAntolini