stripe / stripe-android

Stripe Android SDK
https://stripe.com/docs/mobile/android
MIT License
1.25k stars 637 forks source link

[BUG] Crash when calling PaymentLauncher.confirm(params:) #4739

Closed reiaz-gafar closed 2 years ago

reiaz-gafar commented 2 years ago

Summary

When trying to use the PaymentLauncher to confirm a payment, it crashes the app.

Code to reproduce

paymentLauncher.confirm(confirmParams)

Android version

Using Android 12, happening on Stripe SDK versions 17+

Impacted devices

Happens on all devices tried on.

Installation method

Gradle

Dependency Versions

kotlin: 1.6.10 stripe-android: 19.3.0 Android Gradle Plugin: 7.1.2 Gradle: 7.3.3

SDK classes

PaymentLauncher

Video

Other information

The payment intent in Stripe dashboard indicates that the payment intent was successfully confirmed, but we crash before we receive the callback.

Stack trace:

02-28 11:54:28.177 7422 7422 E AndroidRuntime: FATAL EXCEPTION: main
02-28 11:54:28.177 7422 7422 E AndroidRuntime: Process: com.lightspeed.xseries.dev, PID: 7422
02-28 11:54:28.177 7422 7422 E AndroidRuntime: java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = kotlinx.coroutines.JobCancellationException)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeSerializable(Parcel.java:1850)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.stripe.android.payments.paymentlauncher.PaymentResult$Failed.writeToParcel(Unknown Source:10)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeParcelable(Parcel.java:1818)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeValue(Parcel.java:1724)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeArrayMapInternal(Parcel.java:945)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1584)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Bundle.writeToParcel(Bundle.java:1253)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeBundle(Parcel.java:1014)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.content.Intent.writeToParcel(Intent.java:11155)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.app.IActivityTaskManager$Stub$Proxy.finishActivity(IActivityTaskManager.java:4874)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.app.Activity.finish(Activity.java:6285)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.app.Activity.finish(Activity.java:6309)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.stripe.android.payments.paymentlauncher.PaymentLauncherConfirmationActivity.finish(PaymentLauncherConfirmationActivity.kt:89)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.stripe.android.payments.paymentlauncher.PaymentLauncherConfirmationActivity.finishWithResult(PaymentLauncherConfirmationActivity.kt:108)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.stripe.android.payments.paymentlauncher.PaymentLauncherConfirmationActivity.$r8$lambda$CfpgStE7oOr92cQhCPbhChZ-Eio(Unknown Source:0)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.stripe.android.payments.paymentlauncher.PaymentLauncherConfirmationActivity$$ExternalSyntheticLambda0.onChanged(Unknown Source:4)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:151)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at androidx.lifecycle.LiveData.setValue(LiveData.java:309)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at androidx.lifecycle.LiveData$1.run(LiveData.java:93)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Handler.handleCallback(Handler.java:883)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:100)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Looper.loop(Looper.java:237)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:8107)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Native Method)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
02-28 11:54:28.177 7422 7422 E AndroidRuntime: Caused by: java.io.NotSerializableException: kotlinx.coroutines.SupervisorJobImpl
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1240)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)
02-28 11:54:28.177 7422 7422 E AndroidRuntime:    at android.os.Parcel.writeSerializable(Parcel.java:1845)
jameswoo-stripe commented 2 years ago

Hi @reiaz-gafar, thank you for reporting this issue. Can you provide a little more information:

  1. on what your confirmParams looks like? For example, which payment method you are using
  2. how you constructed paymentLauncher
reiaz-gafar commented 2 years ago

Hi @jameswoo-stripe.

  1. ConfirmPaymentIntentParams(paymentMethodCreateParams=PaymentMethodCreateParams(type=card, card=Card(number=4242424242424242, expiryMonth=2, expiryYear=2042, cvc=424, token=null, attribution=[CardInputView]), ideal=null, fpx=null, sepaDebit=null, auBecsDebit=null, bacsDebit=null, sofort=null, upi=null, netbanking=null, billingDetails=BillingDetails(address=Address(city=null, country=null, line1=null, line2=null, postalCode=, state=null), email=null, name=null, phone=null), metadata=null, productUsage=[], overrideParamMap=null), paymentMethodId=null, sourceParams=null, sourceId=null, clientSecret=pi_xxxxxxxxxxxxxxxxxx_secret_xxxxxxxxxxxxxxxxxx, returnUrl=null, savePaymentMethod=null, useStripeSdk=false, paymentMethodOptions=null, mandateId=null, mandateData=null, setupFutureUsage=null, shipping=null, receiptEmail=null) (I x'd out the client secret after I pasted here)

  2. Payment launcher is constructed using a fragment like so: PaymentLauncher.Companion.create(fragment, publishableKey, stripeAccountId, callback)

jameswoo-stripe commented 2 years ago

@reiaz-gafar, thanks for the information. Been trying to reproduce this issue without any success, it is hard to find out why the coroutine is cancelling without more information.

I can propose a bandaid solution that will just wrap the JobCancellationException into a Throwable, so that when we try to serialize it, it won't crash.

In the meantime, it would be helpful to understand how you are using PaymentLauncher. Anything will help, even pseudocode to help debug the issue, please be as specific as you can. Questions off the top of my head:

reiaz-gafar commented 2 years ago

Thanks for looking into this @jameswoo-stripe. I've created a stripped down sample app where I'm still seeing the issue here: https://github.com/reiaz-gafar/stripe-manual-entry-crash.

It still has the same crash location

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.stripemanualentrysampleapp, PID: 3259
    java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = kotlinx.coroutines.JobCancellationException)

As for our main app where it's crashing to answer your questions:

  1. The PaymentLauncher is being created in the onCreate of the fragment. The launcher itself is being held as a property of the fragment's view model so it's created like so fragmentViewModel.createPaymentLauncher(this). We pass in a reference to the fragment so the view model has the information it needs to create the launcher. This is the function and property in the view model. stripeManualEntry is a service that wraps the SDK calls.
    private var paymentLauncher: PaymentLauncher? = null
    fun createPaymentLauncher(fragment: Fragment) {
        paymentLauncher = stripeManualEntry.createPaymentLauncher(fragment, ::onPaymentResult)
    }
  1. We end up calling paymentLauncher.confirm when the user taps the pay button which we then grab the information from the card widget like so:

        cardInputWidget.paymentMethodCreateParams?.let { paymentMethodCreateParams ->
            applicationScope().launch {
                stripeManualEntry.confirmPayment(paymentMethodCreateParams, paymentIntentService.clientSecret.get(), paymentLauncher!!)
            }
        }
  2. I can't see anything that would cause a cancellation on our end. We're expecting the callback from the payment launcher that we passed in here createPaymentLauncher(fragment, ::onPaymentResult) but the app crashes before we enter the onPaymentResult function. I'm pretty sure that we're following those suggested steps that you listed.

reiaz-gafar commented 2 years ago

There may also be a reason to believe that Locale has something to do with this, is there something in the SDK that uses the locale to construct anything in this flow?

The reason I'm mentioning this is that we have a colleague in the UK that has the exact same code and does NOT crash, but myself and others in the New York City region ARE crashing.

jameswoo-stripe commented 2 years ago

Hi @reiaz-gafar thanks so much for the sample app. I tried it out and was not able to reproduce the issue. I am based in the Seattle region and I am not able to get a crash. I tested multiple emulators, upgraded my kotlin version, but no luck so far.

My machine:

Android emulator:

Here are some questions I have:

  1. Are you running on a M1 machine?
  2. Can you provide details on your emulator or device that you are using?
  3. Are you using a VPN?
  4. Is the request timing out, or are you seeing the crash pretty much right away?
reiaz-gafar commented 2 years ago

@jameswoo-stripe Thanks for continuing to look at this.

To answer your questions:

  1. My machine is:
  1. Android devices - crashes on both 100% of the time:
    • Running it on a physical Samsung Galaxy Tab A, Model SM-5510, Android Version 10
  1. No VPN being used

  2. The request does not time out - it actually successfully confirms the payment intent, verfied on the dashboard, but it crashes before we hit the callback.

image

Here's a video of the crash that I'm seeing in the sample app I posted earlier:

https://user-images.githubusercontent.com/12954273/160052401-49e13a7b-e4b5-4a27-a64c-53f09dea2d46.mov

jameswoo-stripe commented 2 years ago

@reiaz-gafar Thank you so much for the detailed responses! This was extremely valuable information and we have found the issue. This issue seems to happen on landscape mode and not portrait mode. There is a configuration change which causes the JobCancellationException being thrown. We will take a look into getting a fix out, thanks again!

reiaz-gafar commented 2 years ago

@jameswoo-stripe Awesome, that's great to hear! Thanks so much for taking the time to look into this.

Any tentative idea on how long it will take to release?

jameswoo-stripe commented 2 years ago

@reiaz-gafar You can subscribe to this PR: https://github.com/stripe/stripe-android/pull/4776. Once merged we can discuss about releasing a patch fix. In the meantime, can you work on this using portrait mode (e.g. using a phone instead of a tablet)?

reiaz-gafar commented 2 years ago

@jameswoo-stripe Thanks for raising that PR, I have suscribed. Unfortunately our app is locked to landscape mode so we are blocked until this patch is released. Thanks again for looking into this and communicating well!

jameswoo-stripe commented 2 years ago

Hi @reiaz-gafar, the fix is merged and we are scheduling a release for April 4. Once released, feel free to update your dependency. Please let us know if you run into more issues.

reiaz-gafar commented 2 years ago

@jameswoo-stripe Amazing, thanks so much for the quick resolution!

reiaz-gafar commented 2 years ago

@jameswoo-stripe Tried out the new release. Unfortunately I'm still getting a crash, not the same one as before though.

2022-04-05 11:07:51.317 3990-4536/? E/Parcel: Class not found when unmarshalling: com.stripe.android.payments.paymentlauncher.PaymentLauncherContract$Args$IntentConfirmationArgs
    java.lang.ClassNotFoundException: com.stripe.android.payments.paymentlauncher.PaymentLauncherContract$Args$IntentConfirmationArgs
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:454)
        at android.os.Parcel.readParcelableCreator(Parcel.java:3031)
        at android.os.Parcel.readParcelable(Parcel.java:2981)
        at android.os.Parcel.readValue(Parcel.java:2883)
        at android.os.Parcel.readArrayMapInternal(Parcel.java:3261)
        at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
        at android.os.BaseBundle.unparcel(BaseBundle.java:236)
        at android.os.BaseBundle.getString(BaseBundle.java:1160)
        at android.content.Intent.getStringExtra(Intent.java:8548)
        at com.android.server.wm.ActivityStarter.startActivity(ActivityStarter.java:790)
        at com.android.server.wm.ActivityStarter.startActivity(ActivityStarter.java:694)
        at com.android.server.wm.ActivityStarter.startActivityMayWait(ActivityStarter.java:1932)
        at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:625)
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1707)
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1603)
        at com.android.server.wm.ActivityTaskManagerService.startActivity(ActivityTaskManagerService.java:1557)
        at android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:1690)
        at android.os.Binder.execTransactInternal(Binder.java:1056)
        at android.os.Binder.execTransact(Binder.java:1029)
     Caused by: java.lang.ClassNotFoundException: com.stripe.android.payments.paymentlauncher.PaymentLauncherContract$Args$IntentConfirmationArgs
        at java.lang.Class.classForName(Native Method) 
        at java.lang.Class.forName(Class.java:454) 
        at android.os.Parcel.readParcelableCreator(Parcel.java:3031) 
        at android.os.Parcel.readParcelable(Parcel.java:2981) 
        at android.os.Parcel.readValue(Parcel.java:2883) 
        at android.os.Parcel.readArrayMapInternal(Parcel.java:3261) 
        at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292) 
        at android.os.BaseBundle.unparcel(BaseBundle.java:236) 
        at android.os.BaseBundle.getString(BaseBundle.java:1160) 
        at android.content.Intent.getStringExtra(Intent.java:8548) 
        at com.android.server.wm.ActivityStarter.startActivity(ActivityStarter.java:790) 
        at com.android.server.wm.ActivityStarter.startActivity(ActivityStarter.java:694) 
        at com.android.server.wm.ActivityStarter.startActivityMayWait(ActivityStarter.java:1932) 
        at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:625) 
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1707) 
        at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1603) 
        at com.android.server.wm.ActivityTaskManagerService.startActivity(ActivityTaskManagerService.java:1557) 
        at android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:1690) 
        at android.os.Binder.execTransactInternal(Binder.java:1056) 
        at android.os.Binder.execTransact(Binder.java:1029) 

The payment is still succesfully confirmed on the dashboard as well.

I tried the same exact app in portrait mode and it works fine. Any ideas on why it's still happening?

jameswoo-stripe commented 2 years ago

@reiaz-gafar I have tried reproducing the issue you are seeing with a tablet in landscape mode in both our example app and the app that you posted previously. Unfortunately, I am unable to see the issue you are seeing. Have you tried invalidating and restarting Android Studio, clearing the gradle cache, cleaning and rebuilding?

20.0.0 does include some breaking changes, but I can't see anything that would have changed with IntentConfirmationArgs. See the migration guide, just in case.

reiaz-gafar commented 2 years ago

@jameswoo-stripe Let me do a bit more investigation and get back, it can totally be something on our side.

anandkumarnyc commented 2 years ago

Hi @jameswoo-stripe, I see below code in PaymentLauncherConfirmationActivity.kt which changes activity orientation to portrait mode. This might explain why we have this issue.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
            // In Oreo, Activities where `android:windowIsTranslucent=true` can't request
            // orientation. See https://stackoverflow.com/a/50832408/11103900
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        }
keithmacklin commented 2 years ago

Hi @jameswoo-stripe I tried the latest SDK in portrait and it does crash the application for me too. However, if I run it on an emulator that is specifically SDK 26 (Android Oreo), it works fine. That would indicate to me that it is this line that is causing the crash.

What would be the ramifications if you were to just remove the orientation line completely? requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

reiaz-gafar commented 2 years ago

Hi @jameswoo-stripe, my teammates posted some of their findings above, what do you think of them?

jameswoo-stripe commented 2 years ago

@anandkumarnyc, @keithmacklin, @reiaz-gafar, thanks for the follow up. I am consulting with the team to see if we can remove the force portrait mode.

jameswoo-stripe commented 2 years ago

@anandkumarnyc @keithmacklin @reiaz-gafar here is a potential workaround PR up: https://github.com/stripe/stripe-android/pull/4855

jameswoo-stripe commented 2 years ago

@anandkumarnyc @keithmacklin @reiaz-gafar hi folks, https://github.com/stripe/stripe-android/pull/4855 was merged. Please be on the lookout for a release this Monday.

jameswoo-stripe commented 2 years ago

@reiaz-gafar Please update to 20.1.0 and let us know if this fixes your issue.

reiaz-gafar commented 2 years ago

@jameswoo-stripe It's looking pretty good now, thanks! Going to do a bit more testing but everything seems to be in order 🤞

jameswoo-stripe commented 2 years ago

Great, thank you for the update! Feel free to open a new issue if you find one.