flutter-stripe / flutter_stripe

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

Card details incomplete exception when attempting to call createPaymentMethod using token #153

Closed markphillips100 closed 2 years ago

markphillips100 commented 3 years ago

Describe the bug I'm using the Pay plugin to obtain a token as per the readme, only instead of calling confirmPaymentMethod(...) I call createPaymentMethod(params). This throws a "PaymentMethodCreateParamsException: Card details not complete". I assume because the card params are indeed null as I'm using a token.

E/DartMessenger(27053): com.reactnativestripesdk.PaymentMethodCreateParamsException: Card details not complete
E/DartMessenger(27053):     at com.reactnativestripesdk.StripeSdkModule.createPaymentMethod(StripeSdkModule.kt:340)
E/DartMessenger(27053):     at com.flutter.stripe.StripeAndroidPlugin.onMethodCall(StripeAndroidPlugin.kt:63)
E/DartMessenger(27053):     at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/DartMessenger(27053):     at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/DartMessenger(27053):     at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:818)
E/DartMessenger(27053):     at android.os.MessageQueue.nativePollOnce(Native Method)
E/DartMessenger(27053):     at android.os.MessageQueue.next(MessageQueue.java:335)
E/DartMessenger(27053):     at android.os.Looper.loop(Looper.java:183)
E/DartMessenger(27053):     at android.app.ActivityThread.main(ActivityThread.java:7664)
E/DartMessenger(27053):     at java.lang.reflect.Method.invoke(Native Method)

Expected behavior I create my payment intent at the backend from a payment method Id so need to be able to obtain the payment method from the provided token.

Smartphone / tablet

jonasbark commented 3 years ago

Can you please post the relevant dart part of your integration? Can you reproduce this in the example of this repository as well?

markphillips100 commented 3 years ago

Hi @jonasbark . Here's the code I use:

      final token =  paymentResult['paymentMethodData']['tokenizationData']['token'];
      final tokenJson = Map.castFrom(json.decode(token));

      var tokenId = tokenJson['id'];
      print('token: $tokenId');
      final params = stripe.PaymentMethodParams.cardFromToken(
        token: tokenId,
      );

      var stripePaymentMethod = await stripe.Stripe.instance.createPaymentMethod(params);

And this is the output of the tokenId I'm printing in that code so I at least know the Google Pay bit to Stripe is okay:

I/flutter (27053): token: {
I/flutter (27053):   "id": "tok_1J2yQRCQutyqCuyWmHLqLzYQ",
I/flutter (27053):   "object": "token",
I/flutter (27053):   "card": {
I/flutter (27053):     "id": "card_1J2yQRCQutyqCuyW3oPW4rkv",
I/flutter (27053):     "object": "card",
I/flutter (27053):     "address_city": "Mountain View",
I/flutter (27053):     "address_country": "US",
I/flutter (27053):     "address_line1": "1600 Amphitheatre Parkway",
I/flutter (27053):     "address_line1_check": "unchecked",
I/flutter (27053):     "address_line2": null,
I/flutter (27053):     "address_state": "CA",
I/flutter (27053):     "address_zip": "94043",
I/flutter (27053):     "address_zip_check": "unchecked",
I/flutter (27053):     "brand": "Visa",
markphillips100 commented 3 years ago

As far as I can tell there are no examples using this combination of cardFromToken and createPaymentMethod. Were you wanting me to create one?

jonasbark commented 3 years ago

No, thanks - that already helps. I'll try to provide a fix.

markphillips100 commented 3 years ago

@jonasbark not sure if this is related or not but also having a problem with confirmPaymentMethod when using PaymentMethodParams.cardFromMethodId for params. I get a generic (StripeError<PaymentIntentError>(message: Confirming payment intent failed, code: PaymentIntentError.unknown)

I use the same code as here to do the confirmation. Just to note that I call this code from a FlutterBloc, not a widget as in the example code.

To make sure it wasn't just me, I tweaked the example a little in the _handleOffSessionPayment function in order to have the payment intent retrieved:

  Future<void> _handleOffSessionPayment() async {
    final res = await _chargeCardOffSession();
    await _handleRetrievePaymentIntent(res['clientSecret']);

So this setup allows me to save a card, I used 4000 0000 0000 0341 test card. I supply my email and make a payment. Then tap the last button for the recovery flow...and that's when I see the error.

Now there may be something actually wrong with me doing this tweak that causes an error but it's not at the stripe API for sure. Something in SDK only there's no exception detail to determine what. I wondered if it were related to the card not being populated as per the original issue but in the example's case it is populated at the time the confirm is executed.

markphillips100 commented 3 years ago

Investigated the confirm issue above a bit further using AndroidStudio so I can debug into StripeSdkModule.kt and noticed the confirmPaymentMethod in there rejects a promise due to it calling the mapToPaymentMethodType function. The function returns null as no map for "CardId" is found and this is the frozen type for params when created from cardFromMethodId.

I'm uncertain even if one can be added as not sure what CardId maps to on "PaymentMethod.Type"?

markphillips100 commented 3 years ago

Related confirm issue #154

markphillips100 commented 3 years ago

I'm thinking the frozen type for cardFromMethodId should be "Card" as it is for cardFromToken?

markphillips100 commented 3 years ago

I forked and changed local repo dart here to use "Card" rather than "CardId" and I can indeed now call confirmPaymentMethod successfully after using cardFromMethodId.

ibariens commented 3 years ago

I can confirm that the solution of @markphillips100 works. Thanks!

mortifia commented 3 years ago

@markphillips100 after i tested your solution i got that error :

flutter_stripe/packages/stripe_platform_interface/lib/src/method_channel_stripe.dart :84 
  PlatformException(Failed, com.stripe.android.exception.APIException: length=0; index=0, null, null)

do you have a solution to know the exact error coming from stripe or to fix it ?

markphillips100 commented 3 years ago

@mortifia if the error is a response to the Stripe REST api itself then perhaps the Stripe developer dashboard will give a clue. If it's something in the Stripe SDK itself then maybe debugging the app in Android Studio will allow you to dig deeper and discover what the real reason for the error is.

mortifia commented 3 years ago

I finally found the problem it was a problem that came from our code it missed an await on the get of the secret key

thus giving an empty string as secret key (strange error for that)

ibariens commented 3 years ago

I guess this issue can be closed, since creating a payment method from a token will not be supported when I read (https://github.com/stripe/stripe-react-native/issues/349). I created a seperate issue for the PaymentMethodParams.cardFromMethodId problem.

markphillips100 commented 3 years ago

Yes @ibariens , that's what I understood from the stripe repo too. For my use case I just support passing either a payment method Id or a token Id to my backend, and just create the payment method there if a token was supplied. Rest of my workflow is the same then.

Thanks for creating the separate issue too.

remonh87 commented 2 years ago

this should work now . We already have this functionality in the example app

sohailokzz commented 1 month ago

Hi @jonasbark . Here's the code I use:

      final token =  paymentResult['paymentMethodData']['tokenizationData']['token'];
      final tokenJson = Map.castFrom(json.decode(token));

      var tokenId = tokenJson['id'];
      print('token: $tokenId');
      final params = stripe.PaymentMethodParams.cardFromToken(
        token: tokenId,
      );

      var stripePaymentMethod = await stripe.Stripe.instance.createPaymentMethod(params);

And this is the output of the tokenId I'm printing in that code so I at least know the Google Pay bit to Stripe is okay:

I/flutter (27053): token: {
I/flutter (27053):   "id": "tok_1J2yQRCQutyqCuyWmHLqLzYQ",
I/flutter (27053):   "object": "token",
I/flutter (27053):   "card": {
I/flutter (27053):     "id": "card_1J2yQRCQutyqCuyW3oPW4rkv",
I/flutter (27053):     "object": "card",
I/flutter (27053):     "address_city": "Mountain View",
I/flutter (27053):     "address_country": "US",
I/flutter (27053):     "address_line1": "1600 Amphitheatre Parkway",
I/flutter (27053):     "address_line1_check": "unchecked",
I/flutter (27053):     "address_line2": null,
I/flutter (27053):     "address_state": "CA",
I/flutter (27053):     "address_zip": "94043",
I/flutter (27053):     "address_zip_check": "unchecked",
I/flutter (27053):     "brand": "Visa",

Could you provide the complete method snippet..? As I am trying the to get token with .creatToken but some data are missing in my log not in stripe dashboard..?