Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with PaymentIntent as Serializable #386

Closed
Ekalips opened this issue Oct 11, 2023 · 8 comments
Closed

Issue with PaymentIntent as Serializable #386

Ekalips opened this issue Oct 11, 2023 · 8 comments
Assignees

Comments

@Ekalips
Copy link

Ekalips commented Oct 11, 2023

Summary

With the recent Stripe Terminal SDK v3+, PaymentIntent moved from Parcelable to Serializable and it seems like it's causing issues with ProGuard/R8.
It looks like Serializable interface flags get stripped out by ProGuard/R8 causing crash when you try to put a Parcelable that contains PaymentIntent into a Bundle.
It is fixable by adding -keep rules in app's proguard-rules.pro, but it doesn't seem right.

Code to reproduce

@Parcelize
data class ReaderPaymentResult(
    val paymentIntentId: String,
    val paymentAmount: Double,
    val tipAmount: Double,
    val paymentIntent: PaymentIntent
): Parcelable

Android version

Android 9

Impacted devices (Android devices or readers)

Wise POS E

SDK version

3.1.0

Other information

java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = com.stripe.stripeterminal.external.models.PaymentIntent)
                                                                            	at android.os.Parcel.writeSerializable(Parcel.java:1714)
                                                                            	at com.pepperhq.tenants.tenantname.data.model.reader.ReaderPaymentResult.writeToParcel(Unknown Source:22)
                                                                            	at android.os.Parcel.writeParcelable(Parcel.java:1683)
                                                                            	at android.os.Parcel.writeValue(Parcel.java:1589)
                                                                            	at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
                                                                            	at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
                                                                            	at android.os.Bundle.writeToParcel(Bundle.java:1233)
                                                                            	at android.os.Parcel.writeBundle(Parcel.java:915)
                                                                            	at android.content.Intent.writeToParcel(Intent.java:10065)
                                                                            	at android.app.IActivityManager$Stub$Proxy.finishActivity(IActivityManager.java:3790)
                                                                            	at android.app.Activity.finish(Activity.java:5608)
                                                                            	at android.app.Activity.finish(Activity.java:5632)
                                                                            	at uc.s0.invoke(Unknown Source:39)
                                                                            	at fa.v.invokeSuspend(Unknown Source:16)
                                                                            	at bi.a.resumeWith(Unknown Source:8)
                                                                            	at tk.i0.run(Unknown Source:113)
                                                                            	at android.os.Handler.handleCallback(Handler.java:873)
                                                                            	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                            	at android.os.Looper.loop(Looper.java:193)
                                                                            	at android.app.ActivityThread.main(ActivityThread.java:6746)
                                                                            	at java.lang.reflect.Method.invoke(Native Method)
                                                                            	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
                                                                            	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
                                                                            	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [t1{Cancelling}@614aad2, Dispatchers.Main.immediate]
                                                                            Caused by: java.io.NotSerializableException: v0.h
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1240)
                                                                            	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
                                                                            	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
                                                                            	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
                                                                            	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
                                                                            	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
                                                                            	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
                                                                            	at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1434)
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1230)
                                                                            	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
                                                                            	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
                                                                            	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
                                                                            	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
                                                                            	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
                                                                            	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
                                                                            	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
                                                                            	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)
                                                                            	at android.os.Parcel.writeSerializable(Parcel.java:1709)
@chr-stripe chr-stripe self-assigned this Oct 13, 2023
@chr-stripe
Copy link
Collaborator

It is fixable by adding -keep rules in app's proguard-rules.pro, but it doesn't seem right.

@Ekalips can you share which keep rules you had to add in order for this to no longer crash? I can investigate what might be happening here.

@Ekalips
Copy link
Author

Ekalips commented Oct 15, 2023

@chr-stripe I've only tried with a simple "keep all Serializables" rule.

-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

But as you can imagine, it's not ideal. I think keeping only PaymentIntent will help and be better, but ideally I wouldn't want to add any more library specific rules into project's proguard config.

By the way, we are using R8 in fullMode=true if it helps.

@Ekalips
Copy link
Author

Ekalips commented Oct 16, 2023

After stacktrace deobfuscation things became even weirder

Caused by: java.io.NotSerializableException: v0.h

is actually

Caused by: java.io.NotSerializableException: androidx.coordinatorlayout.widget.CoordinatorLayout$ViewElevationComparator
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1240)
	at java.io.ObjectOutputStream.null defaultWriteFields(null)(ObjectOutputStream.java:1604)
	at java.io.ObjectOutputStream.null writeSerialData(null)(ObjectOutputStream.java:1565)
	at java.io.ObjectOutputStream.null writeOrdinaryObject(null)(ObjectOutputStream.java:1488)
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1234)
	at java.io.ObjectOutputStream.null defaultWriteFields(null)(ObjectOutputStream.java:1604)
	at java.io.ObjectOutputStream.null writeSerialData(null)(ObjectOutputStream.java:1565)
	at java.io.ObjectOutputStream.null writeOrdinaryObject(null)(ObjectOutputStream.java:1488)
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1234)
	at java.io.ObjectOutputStream.null writeArray(null)(ObjectOutputStream.java:1434)
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1230)
	at java.io.ObjectOutputStream.null defaultWriteFields(null)(ObjectOutputStream.java:1604)
	at java.io.ObjectOutputStream.null writeSerialData(null)(ObjectOutputStream.java:1565)
	at java.io.ObjectOutputStream.null writeOrdinaryObject(null)(ObjectOutputStream.java:1488)
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1234)
	at java.io.ObjectOutputStream.null defaultWriteFields(null)(ObjectOutputStream.java:1604)
	at java.io.ObjectOutputStream.null writeSerialData(null)(ObjectOutputStream.java:1565)
	at java.io.ObjectOutputStream.null writeOrdinaryObject(null)(ObjectOutputStream.java:1488)
	at java.io.ObjectOutputStream.null writeObject0(null)(ObjectOutputStream.java:1234)
	at java.io.ObjectOutputStream.null writeObject(null)(ObjectOutputStream.java:354)
	at android.os.Parcel.null writeSerializable(null)(Parcel.java:2746)

I have no idea how it happens from this

setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_RESULT, result))

where result is result: ReaderPaymentResult mentioned earlier

@chr-stripe
Copy link
Collaborator

Have you been able to diagnose where that androidx.coordinatorlayout.widget.CoordinatorLayout$ViewElevationComparator is coming from? If you're able to reproduce this issue in a debuggable environment, putting a debugger breakpoint in the writeObject0() method where the exception is thrown might give you a good idea of the Serializable "chain" from the ReaderPaymentResult down to the ViewElevationComparator.

@Ekalips
Copy link
Author

Ekalips commented Oct 30, 2023

@chr-stripe Hey
I'm sorry, my previous de-obfuscated log was incorrect. The new one makes much more sense and actually points to the issue. The root cause is this
Caused by: java.io.NotSerializableException: com.squareup.moshi.LinkedHashTreeMap$1

With the debugging I've made, I suspect that it's the Comparator instance in the LinkedHashTreeMap which is set as a value for the public final val metadata: kotlin.collections.Map<kotlin.String, kotlin.String>? field.

Not sure what version of Moshi lib Stripe uses, but our project has Moshi 1.15.0, which overrides yours.

Hope it helps in debugging it your side.

@Ekalips
Copy link
Author

Ekalips commented Oct 30, 2023

As a result of findings above, also found a better (smaller) proguard config that fixes the issue

-keepclassmembers class * implements java.io.Serializable {
    java.lang.Object writeReplace();
}

Can also probably scope it down even further to just com.squareup.moshi.LinkedHashTreeMap

@chr-stripe
Copy link
Collaborator

We've added two new Proguard rules that will ship with a future release of the Terminal SDK. In the meantime you should be able to add these rules listed below to your own integration until that future release goes live.

For keeping Serialization markers, this rule was suggested in a Guardsquare examples page that seems fairly comprehensive at keeping everything necessary for Serialization:

-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

We've also added one rule from Moshi Issue #1663:

-keep,allowobfuscation,allowshrinking class com.squareup.moshi.JsonAdapter

This was causing issues in R8 fullMode in certain cases. Hopefully this one will be integrated directly into Moshi some day in the future.

@chr-stripe
Copy link
Collaborator

We have shipped these new Proguard rules as part of SDK 3.2.1, so this should now be fully resolved by the latest SDK version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants