stripe-android icon indicating copy to clipboard operation
stripe-android copied to clipboard

[BUG] com.stripe.android.core.exception.APIException in Stripe3ds2TransactionActivity

Open GhassenMsd opened this issue 2 years ago • 13 comments

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

GhassenMsd avatar Nov 15 '22 16:11 GhassenMsd

Hi @GhassenMsd thanks for raising this issue, we will take a look.

jameswoo-stripe avatar Nov 17 '22 11:11 jameswoo-stripe

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 !

GhassenMsd avatar Nov 23 '22 09:11 GhassenMsd

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.

jameswoo-stripe avatar Nov 28 '22 21:11 jameswoo-stripe

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 avatar Dec 01 '22 08:12 GhassenMsd

@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.

jameswoo-stripe avatar Dec 01 '22 18:12 jameswoo-stripe

Hello @jameswoo-stripe here is our dependencies.txt dependencies.txt

GhassenMsd avatar Dec 05 '22 08:12 GhassenMsd

Hello @jameswoo-stripe Do you have any news about this problem please ? Thank you

GhassenMsd avatar Dec 28 '22 08:12 GhassenMsd

@GhassenMsd I apologize for the delay.

I have a few asks for you:

  1. 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 `).

  2. Can you please share manufacturer/models of the devices it's crashing on?

  3. 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 Castle and nimbus-jose-jwt dependencies 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.

jameswoo-stripe avatar Jan 04 '23 18:01 jameswoo-stripe

@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.

jameswoo-stripe avatar Jan 05 '23 18:01 jameswoo-stripe

Any updates on this issue? I'm facing the same issue on Android 13.

Muhammad19Omer avatar Jan 16 '23 02:01 Muhammad19Omer

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?

jameswoo-stripe avatar Feb 28 '23 19:02 jameswoo-stripe

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 !

GhassenMsd avatar Mar 07 '23 09:03 GhassenMsd

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 :/

GhassenMsd avatar Mar 30 '23 15:03 GhassenMsd