stripe-android
stripe-android copied to clipboard
[BUG] com.stripe.android.core.exception.APIException in Stripe3ds2TransactionActivity
Summary
- Hello, we recently received some payment bugs for some users on our production app (since September to be more precise) knowing that nothing has changed on our app. I can't reproduce myself but the bug appears when calling the onPaymentResult after a 3D secure paiement.
We tried to resolve this by adding
-dontwarn com.nimbusds.jose.**in our proguard but the error does not go away.
Code to reproduce
Activity :
stripe.onPaymentResult(requestCode, data,
object : ApiResultCallback<PaymentIntentResult> {
override fun onSuccess(result: PaymentIntentResult) {
val paymentIntent = result.intent
val status = paymentIntent.status
if (status == StripeIntent.Status.Succeeded
|| status == StripeIntent.Status.RequiresCapture) {
// show success UI
paymentValidation()
} else if (StripeIntent.Status.RequiresPaymentMethod == status) {
alertPaymentInvalid()
}
}
override fun onError(e: Exception) {
// handle error
Timber.e(e)
alertPaymentInvalid()
}
})
Proguard :
#### Stripe ####
-keep class com.stripe.** { *; }
-dontwarn com.nimbusds.jose.**
build.gradle :
implementation "com.stripe:stripe-android:20.11.0"
Error (Logs from Bugsnag)
com.stripe.android.core.exception.APIException · com.nimbusds.jose.jwk.ECKeyRawStripeException.kt:59 com.stripe.android.core.exception.StripeException$Companion.createStripe3ds2TransactionViewModel.kt:130 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel.begin3ds2AuthStripe3ds2TransactionViewModel:43 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel.access$getArgs$pStripe3ds2TransactionViewModel:43 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel.access$begin3ds2AuthUnknown:12 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel$begin3ds2Auth$1.invokeSuspendContinuationImpl.kt:33 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWithDispatchedTask.kt:104 kotlinx.coroutines.DispatchedTask.runDispatchQueue.kt:75 androidx.lifecycle.DispatchQueue.drainQueueDispatchQueue.kt:112 androidx.lifecycle.DispatchQueue.enqueueDispatchQueue:100 androidx.lifecycle.DispatchQueue.dispatchAndEnqueue$lambda-2$lambda-1Camera2CameraControlImpl:29 androidx.camera.camera2.internal.Camera2CameraControlImpl$InternalSyntheticLambda$9$0f38b5e36e943682c84f554179cb78861023d0f0e75b02180fb29b5cb9384385$0.run$bridgeHandler.java:789 android.os.Handler.handleCallbackHandler.java:98 android.os.Handler.dispatchMessageLooper.java:164 android.os.Looper.loopActivityThread.java:6944 android.app.ActivityThread.mainMethod.java:-2 java.lang.reflect.Method.invokeZygote.java:327 com.android.internal.os.Zygote$MethodAndArgsCaller.runZygoteInit.java:1374 com.android.internal.os.ZygoteInit.mainCaused By: java.lang.NoClassDefFoundError · com.nimbusds.jose.jwk.ECKeyECKey.java:602 com.nimbusds.jose.jwk.ECKey.encodeCoordinateECKey:273 com.nimbusds.jose.jwk.ECKey$Builder.<init>DefaultAuthenticationRequestParametersFactory.kt:186 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$Companion.createPublicJwk$3ds2sdk_releaseDefaultAuthenticationRequestParametersFactory.kt:138 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeSuspendUnknown:8 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeUnknown:4 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeUndispatchedKt:89 kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnBuildersKt__Builders_commonKt:169 kotlinx.coroutines.BuildersKt__Builders_commonKt.withContextBuildersKt:1 kotlinx.coroutines.BuildersKt.withContextDefaultAuthenticationRequestParametersFactory:108 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory.createStripeTransaction.kt:16 com.stripe.android.stripe3ds2.transaction.StripeTransaction.createAuthenticationRequestParametersStripe3ds2TransactionViewModel.kt:145 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel$perform3ds2AuthenticationRequest$2.invokeSuspendContinuationImpl.kt:33 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWithDispatchedTask.kt:106 kotlinx.coroutines.DispatchedTask.runLimitedDispatcher.kt:42 kotlinx.coroutines.internal.LimitedDispatcher.runTasks.kt:95 kotlinx.coroutines.scheduling.TaskImpl.runCoroutineScheduler:570 kotlinx.coroutines.scheduling.CoroutineScheduler.runSafelyCoroutineScheduler.kt:750 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTaskCoroutineScheduler.kt:677 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorkerCoroutineScheduler.kt:664 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runCaused By: java.lang.VerifyError · Verifier rejected class com.nimbusds.jose.jwk.ECKey: com.nimbusds.jose.jwk.ECKey com.nimbusds.jose.jwk.ECKey.parse(java.security.cert.X509Certificate) failed to verify: com.nimbusds.jose.jwk.ECKey com.nimbusds.jose.jwk.ECKey.parse(java.security.cert.X509Certificate): [0x17] cannot access instance field benj.L3 benj.fw0.c from object of type Unresolved Reference: org.bouncycastle.asn1.x509.SubjectPublicKeyInfo (declaration of 'com.nimbusds.jose.jwk.ECKey' appears in /data/app/com.wayzup.wayzupapp-Ml4iMYgO-vrqxEgo6ew5pg==/base.apk:classes2.dex)ECKey.java:602 com.nimbusds.jose.jwk.ECKey.encodeCoordinateECKey:273 com.nimbusds.jose.jwk.ECKey$Builder.<init>DefaultAuthenticationRequestParametersFactory.kt:186 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$Companion.createPublicJwk$3ds2sdk_releaseDefaultAuthenticationRequestParametersFactory.kt:138 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeSuspendUnknown:8 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeUnknown:4 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory$create$2.invokeUndispatchedKt:89 kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnBuildersKt__Builders_commonKt:169 kotlinx.coroutines.BuildersKt__Builders_commonKt.withContextBuildersKt:1 kotlinx.coroutines.BuildersKt.withContextDefaultAuthenticationRequestParametersFactory:108 com.stripe.android.stripe3ds2.transaction.DefaultAuthenticationRequestParametersFactory.createStripeTransaction.kt:16 com.stripe.android.stripe3ds2.transaction.StripeTransaction.createAuthenticationRequestParametersStripe3ds2TransactionViewModel.kt:145 com.stripe.android.payments.core.authentication.threeds2.Stripe3ds2TransactionViewModel$perform3ds2AuthenticationRequest$2.invokeSuspendContinuationImpl.kt:33 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWithDispatchedTask.kt:106 kotlinx.coroutines.DispatchedTask.runLimitedDispatcher.kt:42 kotlinx.coroutines.internal.LimitedDispatcher.runTasks.kt:95 kotlinx.coroutines.scheduling.TaskImpl.runCoroutineScheduler:570 kotlinx.coroutines.scheduling.CoroutineScheduler.runSafelyCoroutineScheduler.kt:750 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTaskCoroutineScheduler.kt:677 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorkerCoroutineScheduler.kt:664 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run
Android version
More than 80 of users are impacted, mostly Android 7 and 8.
Installation method
Gradle Dependency
Dependency Versions
kotlin: 1.5.31 Gradle: 7.3.3 stripe-android: 20.11.0 Android Gradle Plugin: 7.2.2
SDK classes
- Stripe3ds2TransactionActivity
Hi @GhassenMsd thanks for raising this issue, we will take a look.
Hello @jameswoo-stripe ! Do you have any news about this bug? we are receiving a lot of calls from users blocked on payment process because of this issue, we have almost 100 users affected now, I don't know if it can help but we noticed that they are all on Android 7 and 8. Can you help me to understand what is happening please ? Thanks !
Hi @GhassenMsd can you post your build.gradle?
A possible reason for the issue be due to a gradle dependency issue. Our 3DS2 module uses: implementation "com.nimbusds:nimbus-jose-jwt:9.21". Can you verify that there isn't a conflict here?
We haven't updated the 3DS2 module in a while and you mentioned that the issues started appearing in September. My best guess at this stage is that there is some sort of gradle dependency conflict.
Hello @jameswoo-stripe Thank you for your answer, i couldn't see a conflict issue in our gradle. Here is our build.gradle when the bug appeared before the update of the latest version :
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id "io.gitlab.arturbosch.detekt" version "1.19.0"
}
apply plugin: 'com.android.application'
apply plugin: 'com.bugsnag.android.gradle'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' //TODO deprecated, remove when migration to view binding is finished
// Realm https://docs.mongodb.com/realm/sdk/java/install/
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
apply from: './jacoco.gradle'
android {
defaultConfig {
versionCode 101000
versionName "10.10.0"
minSdkVersion 21
targetSdkVersion 32
//testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" //Locale
testInstrumentationRunner "com.wayzup.helpers.TestRunner" //Circle
multiDexEnabled true
renderscriptTargetApi 27
renderscriptSupportModeEnabled true
testOptions {
//from https://circleci.com/docs/2.0/language-android/
unitTests {
all {
maxHeapSize = "1024m"
jacoco {
includeNoLocationClasses = true
}
}
}
unitTests.returnDefaultValues = true
}
}
compileSdkVersion 32
buildToolsVersion '30.0.3'
buildFeatures {
viewBinding = true
}
// Enable NDK build
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
}
flavorDimensions "default"
productFlavors {
// Different productFlavors are used to parallelize tests on CI
primary {
dimension "default"
}
secondary {
dimension "default"
}
tertiary {
dimension "default"
}
four {
dimension "default"
}
facebook {
dimension "default"
//Facebook tests
}
}
testOptions {
unitTests.returnDefaultValues = true
animationsDisabled = true
}
//testBuildType "playground" //Run tests on playground env
}
dependencies {
repositories {
mavenCentral()
google()
maven {
url 'https://api.mapbox.com/downloads/v2/releases/maven'
authentication {
basic(BasicAuthentication)
}
credentials {
username = 'mapbox'
// Use the secret token you stored in gradle.properties as the password (Android Test CI token)
password = project.properties['MAPBOX_DOWNLOADS_TOKEN']
}
}
jcenter() // AppKillerManager
}
//PROD
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.google.android.gms:play-services-location:19.0.1"
implementation "com.google.firebase:firebase-messaging:23.0.8"
implementation "com.google.firebase:firebase-config:21.1.2"
// App Review
implementation("com.google.android.play:review:2.0.0")
implementation("com.google.android.play:review-ktx:2.0.0")
// Google maps
implementation "com.google.android.gms:play-services-maps:18.0.2"
implementation "com.google.maps.android:android-maps-utils:2.3.0"
implementation "com.cocoahero.android:geojson:1.0.1@jar"
implementation "com.android.volley:volley:1.2.1"
implementation "com.bugsnag:bugsnag-android:5.26.0"
implementation "com.google.code.gson:gson:2.9.1"
implementation "com.facebook.android:facebook-login:13.1.0" //13.2.0 sdkInitialize error
implementation "io.card:android-sdk:5.5.1"
implementation "com.stripe:stripe-android:20.11.0"
implementation "com.esotericsoftware.kryo:kryo:2.24.0" //5.3.0
implementation "com.github.CanHub:Android-Image-Cropper:3.3.6"
implementation "com.google.guava:guava:31.1-android"
implementation "androidx.multidex:multidex:2.0.1"
ext.mapbox_version = '10.7.0'
implementation "com.mapbox.maps:android:$mapbox_version"
implementation "com.mapbox.plugin:maps-logo:$mapbox_version"
implementation "com.mapbox.plugin:maps-attribution:$mapbox_version"
implementation "com.mapbox.plugin:maps-scalebar:$mapbox_version"
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:6.6.0' //TODO move to Search SDK ?
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:6.4.0'
implementation "me.saket:better-link-movement-method:2.2.0"
implementation "net.openid:appauth:0.11.1"
//Branch
implementation "io.branch.sdk.android:library:5.2.3"
//Branch : required if your app is in the Google Play Store
implementation "com.google.firebase:firebase-appindexing:20.0.0"
implementation "com.google.android.gms:play-services-ads-identifier:18.0.1"
//Branch : Chrome Tab matching (enables 100% guaranteed matching based on cookies)
implementation "androidx.browser:browser:1.4.0"
implementation "com.airbnb.android:lottie:5.2.0"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'com.pusher:pusher-java-client:2.2.8'
//Amplitude
implementation 'com.amplitude:android-sdk:2.37.0'
//Firebase text recognition
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
//Blur effect
implementation 'jp.wasabeef:blurry:4.0.1'
// Detect battery optimization
implementation 'com.thelittlefireman:AppKillerManager:2.1.1'
//Camera in-app
api 'com.otaliastudios:cameraview:2.7.2'
//Event bus
implementation "org.greenrobot:eventbus:3.3.1"
// Android Snooper library for Okhttp
implementation('com.github.jainsahab:Snooper-Okhttp:1.5.6@aar') {
transitive = true
}
// Detect app in foreground / background
implementation "android.arch.lifecycle:extensions:1.1.1"
// NTP
implementation "com.lyft.kronos:kronos-android:0.0.1-alpha11"
// Fraud detection
implementation 'com.github.scottyab:safetynethelper:0.8.0'
// Auto grid view
implementation 'com.google.android.flexbox:flexbox:3.0.0'
// Wheel scroll choices
implementation 'com.webianks.library:scroll-choice:1.0.1'
// Contacts
implementation 'com.github.vestrel00.contacts-android:core:0.2.3'
// Format phone numbers
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54'
// WorkManager
implementation 'androidx.work:work-runtime:2.7.1'
def camerax_version = "1.0.2"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha32"
//TEST
testAnnotationProcessor "com.google.auto.service:auto-service:1.0.1"
// Required for local unit tests (JUnit 4 framework)
testImplementation 'junit:junit:4.13.2'
testImplementation "org.mockito:mockito-core:4.6.1"
testImplementation "org.robolectric:robolectric:4.8.1"
testImplementation "org.apache.commons:commons-lang3:3.12.0"
// Required for instrumented tests
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
// Mock dependencies
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
// Build and run Espresso tests
def espresso_version = "3.4.0"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-web:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
// Build and run UI Automator tests
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
// To avoid warning
androidTestImplementation 'androidx.annotation:annotation:1.3.0'
//DEBUG
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}
apply from: './code_quality_tools/quality.gradle'
apply plugin: 'com.google.gms.google-services'
repositories {
mavenCentral()
}
detekt {
config = files("code_quality_tools/detekt.yml")
input = files("src/main/java")
}
bugsnag {
// From https://docs.bugsnag.com/build-integrations/gradle/
variantFilter { variant ->
// disables plugin for ci or facebook variants
def name = variant.name.toLowerCase()
if (name.contains("ci") || name.contains("facebook")) {
enabled = false
}
}
overwrite = true
}
And here is our build.gradle (Project: MyApplication)
buildscript {
ext.kotlin_version = '1.7.10'
ext.jacocoVersion = '0.8.8'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.14'
classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.3.0"
classpath "io.realm:realm-gradle-plugin:10.11.1" //10.12
classpath "org.jacoco:org.jacoco.core:$jacocoVersion"
}
}
allprojects {
repositories {
maven { url 'https://jitpack.io' }
mavenCentral()
}
}
@GhassenMsd can you share your app dependency tree using a command like so:
// Note: Replace app with the project module name.
./gradlew app:dependencies > dependencies.txt
And upload the dependencies.txt here so I can inspect it.
Hello @jameswoo-stripe here is our dependencies.txt
dependencies.txt
Hello @jameswoo-stripe Do you have any news about this problem please ? Thank you
@GhassenMsd I apologize for the delay.
I have a few asks for you:
-
Can you please share a formatted stacktrace, so that it is easier to read? Please use a triple back-tick instead of single back-tick (``` vs `).
-
Can you please share manufacturer/models of the devices it's crashing on?
-
I spoke with another engineer on our team and the fact that it is only happening on Android 7 and 8 could point to a security provider issue. We have reason to suspect that there are some dependencies that you are using that are not playing well together.
One thing you could try is to explicitly add both the
Bouncy Castleandnimbus-jose-jwtdependencies in your build script. I am not certain which versions you will need, but you could try some of the following:a. Add to top level build.gradle:
configurations.all { resolutionStrategy { force 'org.bouncycastle:bcprov-jdk15on:1.68' } }OR
configurations.all { resolutionStrategy { force 'org.bouncycastle:bcprov-jdk15to18:1.68' } }b. Add to your app build.gradle:
implementation('org.bouncycastle:bcprov-jdk15on:1.68') { force = true }OR
implementation('org.bouncycastle:bcprov-jdk15to18:1.68') { force = true }It would be best if you were able to get an Android 7 or 8 device to be able to reproduce this issue locally to see how the different dependencies work.
We are also looking into a patch that can redirect all Android 7 and 8 through 3DS1 instead of the 3DS2 SDK, to bypass this issue altogether. I will let you know what I find.
@GhassenMsd Unfortunately, there is no way to fallback through to 3DS1 or to a web fallback in this case. It seems like an issue isolated to Android 7 and 8, when creating a AReq in Stripe's 3DS2 SDK, since that is using Bouncy Castle to encrypt the AReq data.
I will try to reproduce this issue with an Android 7 or 8 device, but if at all possible, it would be better to reproduce this locally yourself so that we can work on this issue together.
Another option is to add a patch for only Android 7 and 8 devices, but I would prefer to see if we can fix this issue altogether.
Any updates on this issue? I'm facing the same issue on Android 13.
Hi @GhassenMsd
Do you have any radar rules that force 3DS transactions on some Android devices? I am still not sure why it is failing for only certain Android devices.
We think this is always happening for all 3DS transactions because your app is not keeping the class references from the com.nimbusds.jose package.
One potential workaround you can try is to add a keep rule in your proguard:
-keep class com.nimbusds.jose.** { *; }
Can you please try this and report back?
Hello @jameswoo-stripe
Thank you for your answer, no we don't have any radar rules forcing 3DS transactions. If it can help, here are some statistics about the android versions impacted by this bug :
76.7% -> Android 8.0.0
10.3% -> Android 7.0
6.6% -> Android 7.1.1
5% -> Android 7.1.2
1.4% -> Android 8.1.0
However, we will try to pass the keep rule on our next release and I will report back to you, thank you !
Hello @jameswoo-stripe
We passed the keep rule -keep class com.nimbusds.jose.** { *; } in our proguard on our latest release, but the problem does not disappear, it is still present as usual on Android 7 and 8 :/