From 818b3f37bb06d66e12e691e0fdc1ce3cc01090ce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:59:50 +0000 Subject: [PATCH 01/95] Update dependency androidx.constraintlayout:constraintlayout to v2.2.0 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 392905cef4..1ed7d8c668 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +35,7 @@ fragment = "1.8.5" lifecycle = "2.8.6" material = "1.12.0" recyclerview = "1.3.2" -constraintlayout = "2.1.4" +constraintlayout = "2.2.0" # Compose dependencies compose-activity = "1.9.3" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 4d7e16d399..ea564b649e 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -278,6 +278,11 @@ + + + + + @@ -2544,6 +2549,14 @@ + + + + + + + + @@ -2552,6 +2565,14 @@ + + + + + + + + @@ -4324,6 +4345,14 @@ + + + + + + + + From b5c707457b0ca2d42c99b51d7fde795954a74289 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:44:56 +0000 Subject: [PATCH 02/95] Update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.8.7 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 155 +++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ed7d8c668..03c5d4ec93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ constraintlayout = "2.2.0" compose-activity = "1.9.3" compose-bom = "2024.10.00" compose-hilt = "1.2.0" -compose-viewmodel = "2.8.6" +compose-viewmodel = "2.8.7" # Adyen dependencies adyen3ds2 = "2.2.21" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index ea564b649e..18c4526bb2 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -282,6 +282,9 @@ + + + @@ -3296,6 +3299,14 @@ + + + + + + + + @@ -3341,6 +3352,14 @@ + + + + + + + + @@ -3373,6 +3392,14 @@ + + + + + + + + @@ -3437,6 +3464,14 @@ + + + + + + + + @@ -3522,6 +3557,14 @@ + + + + + + + + @@ -3570,6 +3613,14 @@ + + + + + + + + @@ -3633,6 +3684,14 @@ + + + + + + + + @@ -3721,6 +3780,14 @@ + + + + + + + + @@ -3753,6 +3820,14 @@ + + + + + + + + @@ -3763,6 +3838,11 @@ + + + + + @@ -3779,6 +3859,14 @@ + + + + + + + + @@ -3851,6 +3939,14 @@ + + + + + + + + @@ -3883,6 +3979,14 @@ + + + + + + + + @@ -3988,6 +4092,17 @@ + + + + + + + + + + + @@ -4020,6 +4135,14 @@ + + + + + + + + @@ -4060,6 +4183,14 @@ + + + + + + + + @@ -4092,6 +4223,14 @@ + + + + + + + + @@ -4164,6 +4303,14 @@ + + + + + + + + @@ -4241,6 +4388,14 @@ + + + + + + + + From 70d1d6cef37da5c817f9496bac92e6a00a6438b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:15:54 +0000 Subject: [PATCH 03/95] Update android.gradle.plugin to v8.7.2 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 330 +++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 03c5d4ec93..6c148c8d24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ version-code = "1" version-name = "5.8.0" # Build script -android-gradle-plugin = "8.7.1" +android-gradle-plugin = "8.7.2" kotlin = "1.9.25" ksp = "1.9.25-1.0.20" dokka = "1.9.20" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 18c4526bb2..df8100605a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2817,6 +2817,14 @@ + + + + + + + + @@ -2873,6 +2881,14 @@ + + + + + + + + @@ -2929,6 +2945,14 @@ + + + + + + + + @@ -5269,6 +5293,14 @@ + + + + + + + + @@ -5325,6 +5357,14 @@ + + + + + + + + @@ -5360,6 +5400,11 @@ + + + + + @@ -5416,6 +5461,14 @@ + + + + + + + + @@ -5451,6 +5504,11 @@ + + + + + @@ -6067,6 +6125,14 @@ + + + + + + + + @@ -6520,6 +6586,14 @@ + + + + + + + + @@ -6576,6 +6650,14 @@ + + + + + + + + @@ -6632,6 +6714,14 @@ + + + + + + + + @@ -6688,6 +6778,14 @@ + + + + + + + + @@ -6824,6 +6922,14 @@ + + + + + + + + @@ -6904,6 +7010,14 @@ + + + + + + + + @@ -6960,6 +7074,14 @@ + + + + + + + + @@ -7016,6 +7138,14 @@ + + + + + + + + @@ -7248,6 +7378,14 @@ + + + + + + + + @@ -8040,6 +8178,14 @@ + + + + + + + + @@ -8088,6 +8234,14 @@ + + + + + + + + @@ -8144,6 +8298,14 @@ + + + + + + + + @@ -8192,6 +8354,14 @@ + + + + + + + + @@ -8248,6 +8418,14 @@ + + + + + + + + @@ -8256,6 +8434,14 @@ + + + + + + + + @@ -8264,6 +8450,14 @@ + + + + + + + + @@ -8312,6 +8506,14 @@ + + + + + + + + @@ -8368,6 +8570,14 @@ + + + + + + + + @@ -8416,6 +8626,14 @@ + + + + + + + + @@ -8472,6 +8690,14 @@ + + + + + + + + @@ -8520,6 +8746,14 @@ + + + + + + + + @@ -8576,6 +8810,14 @@ + + + + + + + + @@ -8624,6 +8866,14 @@ + + + + + + + + @@ -8672,6 +8922,14 @@ + + + + + + + + @@ -8720,6 +8978,14 @@ + + + + + + + + @@ -8776,6 +9042,14 @@ + + + + + + + + @@ -8824,6 +9098,14 @@ + + + + + + + + @@ -8880,6 +9162,14 @@ + + + + + + + + @@ -8928,6 +9218,14 @@ + + + + + + + + @@ -8984,6 +9282,14 @@ + + + + + + + + @@ -9032,6 +9338,14 @@ + + + + + + + + @@ -9088,6 +9402,14 @@ + + + + + + + + @@ -9136,6 +9458,14 @@ + + + + + + + + From f5ad938ee841c967eb7e8a5b86e1baefd752de88 Mon Sep 17 00:00:00 2001 From: jreij Date: Tue, 10 Dec 2024 16:59:31 +0000 Subject: [PATCH 04/95] Update verification metadata --- gradle/verification-metadata.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index df8100605a..76d39069ea 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -6450,6 +6450,17 @@ + + + + + + + + + + + @@ -8002,6 +8013,14 @@ + + + + + + + + From 84e04b6e2ce01c3434b5ce83341e95699df98728 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 3 Dec 2024 14:31:16 +0100 Subject: [PATCH 05/95] Track error events when challenge handling fails COAND-1010 --- .../internal/analytics/ThreeDS2Events.kt | 7 ++ .../internal/ui/DefaultAdyen3DS2Delegate.kt | 15 +++- .../ui/DefaultAdyen3DS2DelegateTest.kt | 71 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt index 9b3a8a0f6b..a28f2e61a9 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt @@ -11,6 +11,8 @@ package com.adyen.checkout.adyen3ds2.internal.analytics import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.internal.analytics.AnalyticsEvent import com.adyen.checkout.components.core.internal.analytics.DirectAnalyticsEventCreation +import com.adyen.checkout.components.core.internal.analytics.ErrorEvent +import com.adyen.checkout.components.core.internal.analytics.GenericEvents @OptIn(DirectAnalyticsEventCreation::class) @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -40,6 +42,11 @@ internal object ThreeDS2Events { message = message, ) + fun threeDS2ChallengeError(event: ErrorEvent) = GenericEvents.error( + component = "threeDS2Challenge", + event = event, + ) + enum class SubType(val value: String) { FINGERPRINT_DATA_SENT("fingerprintDataSentMobile"), FINGERPRINT_COMPLETED("fingerprintCompleted"), diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index bc1f82afa7..617a75c2ba 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -36,6 +36,7 @@ import com.adyen.checkout.components.core.internal.PaymentDataRepository import com.adyen.checkout.components.core.internal.SavedStateHandleContainer import com.adyen.checkout.components.core.internal.SavedStateHandleProperty import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager +import com.adyen.checkout.components.core.internal.analytics.ErrorEvent import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.util.bufferedChannel import com.adyen.checkout.core.AdyenLogLevel @@ -459,6 +460,7 @@ internal class DefaultAdyen3DS2Delegate( ) analyticsManager?.trackEvent(challengeDisplayedEvent) } catch (e: InvalidInputException) { + trackChallengeHandlingFailedErrorEvent() emitError(CheckoutException("Error starting challenge", e)) } } @@ -492,6 +494,7 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(transactionStatus) emitDetails(details) } catch (e: CheckoutException) { + trackChallengeHandlingFailedErrorEvent() emitError(e) } finally { closeTransaction() @@ -510,6 +513,7 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { + trackChallengeHandlingFailedErrorEvent() emitError(e) } finally { closeTransaction() @@ -517,11 +521,12 @@ internal class DefaultAdyen3DS2Delegate( } private fun onError(result: ChallengeResult.Error) { - adyenLog(AdyenLogLevel.DEBUG) { "challenge timed out" } + adyenLog(AdyenLogLevel.DEBUG) { "challenge error" } try { val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { + trackChallengeHandlingFailedErrorEvent() emitError(e) } finally { closeTransaction() @@ -573,6 +578,14 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackChallengeHandlingFailedErrorEvent() = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_CHALLENGE_HANDLING) + + private fun trackChallengeErrorEvent(errorEvent: ErrorEvent) { + val event = ThreeDS2Events.threeDS2ChallengeError(errorEvent) + analyticsManager?.trackEvent(event) + } + private fun closeTransaction() { currentTransaction?.close() currentTransaction = null diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 3abea32cd7..9fd58d4af7 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -30,6 +30,7 @@ import com.adyen.checkout.components.core.action.Threeds2ChallengeAction import com.adyen.checkout.components.core.action.Threeds2FingerprintAction import com.adyen.checkout.components.core.internal.ActionObserverRepository import com.adyen.checkout.components.core.internal.PaymentDataRepository +import com.adyen.checkout.components.core.internal.analytics.ErrorEvent import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper @@ -78,6 +79,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import java.io.IOException @@ -646,6 +648,75 @@ internal class DefaultAdyen3DS2DelegateTest( ) analyticsManager.assertLastEventEquals(expectedEvent) } + + @Test + fun `when challenge fails, then error event is tracked`() = runTest { + initializeChallengeTransaction(this).apply { + shouldThrowError = true + } + + // We need to set the messageVersion to workaround an error in the 3DS2 SDK + delegate.challengeShopper(Activity(), Base64.encode("{\"messageVersion\":\"2.1.0\"}".toByteArray())) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when completed and creating details fails, then error event is tracked`() = runTest { + val adyen3DS2Serializer: Adyen3DS2Serializer = mock() + whenever(adyen3DS2Serializer.createChallengeDetails(any(), anyOrNull())) doAnswer { + throw ComponentException("test") + } + delegate = createDelegate(adyen3DS2Serializer) + + delegate.onCompletion( + result = ChallengeResult.Completed("transactionStatus"), + ) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when timed out and creating details fails, then error event is tracked`() = runTest { + val adyen3DS2Serializer: Adyen3DS2Serializer = mock() + whenever(adyen3DS2Serializer.createChallengeDetails(any(), any())) doAnswer { + throw ComponentException("test") + } + delegate = createDelegate(adyen3DS2Serializer) + + delegate.onCompletion( + result = ChallengeResult.Timeout("transactionStatus", "additionalDetails"), + ) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when error and creating details fails, then error event is tracked`() = runTest { + val adyen3DS2Serializer: Adyen3DS2Serializer = mock() + whenever(adyen3DS2Serializer.createChallengeDetails(any(), any())) doAnswer { + throw ComponentException("test") + } + delegate = createDelegate(adyen3DS2Serializer) + + delegate.onCompletion( + result = ChallengeResult.Error("transactionStatus", "additionalDetails"), + ) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test From fbb85835fac3bc4ea55dee16980f1d1b2ce4f029 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 3 Dec 2024 14:51:26 +0100 Subject: [PATCH 06/95] Track error events when token decoding fails COAND-1010 --- .../internal/analytics/ThreeDS2Events.kt | 5 ++++ .../internal/ui/DefaultAdyen3DS2Delegate.kt | 15 ++++++++++- .../ui/DefaultAdyen3DS2DelegateTest.kt | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt index a28f2e61a9..3980183604 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt @@ -42,6 +42,11 @@ internal object ThreeDS2Events { message = message, ) + fun threeDS2FingerprintError(event: ErrorEvent) = GenericEvents.error( + component = "threeDS2Fingerprint", + event = event, + ) + fun threeDS2ChallengeError(event: ErrorEvent) = GenericEvents.error( component = "threeDS2Challenge", event = event, diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 617a75c2ba..cd590e0bcb 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -229,6 +229,7 @@ internal class DefaultAdyen3DS2Delegate( val fingerprintToken = try { decodeFingerprintToken(encodedFingerprintToken) } catch (e: CheckoutException) { + trackFingerprintTokenDecodeErrorEvent() emitError(ComponentException("Failed to decode fingerprint token", e)) return } @@ -436,7 +437,8 @@ internal class DefaultAdyen3DS2Delegate( val challengeTokenJson: JSONObject = try { JSONObject(decodedChallengeToken) } catch (e: JSONException) { - emitError(ComponentException("JSON parsing of FingerprintToken failed", e)) + trackChallengeTokenDecodeErrorEvent() + emitError(ComponentException("JSON parsing of challenge token failed", e)) return } @@ -578,6 +580,17 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackFingerprintTokenDecodeErrorEvent() = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) + + private fun trackFingerprintErrorEvent(errorEvent: ErrorEvent) { + val event = ThreeDS2Events.threeDS2FingerprintError(errorEvent) + analyticsManager?.trackEvent(event) + } + + private fun trackChallengeTokenDecodeErrorEvent() = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) + private fun trackChallengeHandlingFailedErrorEvent() = trackChallengeErrorEvent(ErrorEvent.THREEDS2_CHALLENGE_HANDLING) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 9fd58d4af7..32243a90e6 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -649,6 +649,31 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when fingerprint token can't be decoded, then error event is tracked`() = runTest { + delegate.initialize(this) + + val encodedJson = Base64.encode("{incorrectJson}".toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TOKEN_DECODING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when challenge token can't be decoded, then error event is tracked`() = runTest { + initializeChallengeTransaction(this) + + delegate.challengeShopper(Activity(), Base64.encode("token".toByteArray())) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_TOKEN_DECODING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when challenge fails, then error event is tracked`() = runTest { initializeChallengeTransaction(this).apply { From 9b25b268620f0091b7297f1a8dc0ce4fbc70157b Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 3 Dec 2024 15:03:28 +0100 Subject: [PATCH 07/95] Track error events when transaction is missing COAND-1010 --- .../adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt | 4 ++++ .../internal/ui/DefaultAdyen3DS2DelegateTest.kt | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index cd590e0bcb..5a84e72a57 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -427,6 +427,7 @@ internal class DefaultAdyen3DS2Delegate( adyenLog(AdyenLogLevel.DEBUG) { "challengeShopper" } if (currentTransaction == null) { + trackChallengeTransactionMissingErrorEvent() emitError( Authentication3DS2Exception("Failed to make challenge, missing reference to initial transaction."), ) @@ -588,6 +589,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackChallengeTransactionMissingErrorEvent() = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING) + private fun trackChallengeTokenDecodeErrorEvent() = trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 32243a90e6..f85db4ff95 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -649,6 +649,16 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when transaction is null, then error event is tracked`() = runTest { + delegate.challengeShopper(mock(), "token") + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_TRANSACTION_MISSING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when fingerprint token can't be decoded, then error event is tracked`() = runTest { delegate.initialize(this) From ff1d6ee31990cae3019aaaa912ab52a93b52c5aa Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 3 Dec 2024 15:10:21 +0100 Subject: [PATCH 08/95] Track error events when fingerprint handling fails COAND-1010 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 8 +++++++- .../ui/DefaultAdyen3DS2DelegateTest.kt | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 5a84e72a57..02d5d4ddf5 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -375,7 +375,10 @@ internal class DefaultAdyen3DS2Delegate( ) .fold( onSuccess = { result -> onSubmitFingerprintResult(result, activity) }, - onFailure = { e -> emitError(ComponentException("Unable to submit fingerprint", e)) }, + onFailure = { e -> + trackFingerprintHandlingErrorEvent() + emitError(ComponentException("Unable to submit fingerprint", e)) + }, ) } @@ -581,6 +584,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackFingerprintHandlingErrorEvent() = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING) + private fun trackFingerprintTokenDecodeErrorEvent() = trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index f85db4ff95..9c6aead922 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -649,6 +649,24 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when fingerprint is submitted automatically and it fails, then error event is tracked`() = runTest { + threeDS2Service.transactionResult = + TransactionResult.Success(TestTransaction(getAuthenticationRequestParams())) + whenever(submitFingerprintRepository.submitFingerprint(any(), any(), anyOrNull())) doReturn + Result.failure(IOException("test")) + + delegate.initialize(this) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, true) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when transaction is null, then error event is tracked`() = runTest { delegate.challengeShopper(mock(), "token") From 905a7b204f825f5b35e6ecbe18ff77d999c4fbc8 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 3 Dec 2024 15:29:13 +0100 Subject: [PATCH 09/95] Track error events when transaction creation fails COAND-1010 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 6 ++ .../ui/DefaultAdyen3DS2DelegateTest.kt | 91 ++++++++++++++++--- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 02d5d4ddf5..abf457bef8 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -314,6 +314,7 @@ internal class DefaultAdyen3DS2Delegate( private fun createTransaction(fingerprintToken: FingerprintToken): Transaction? { if (fingerprintToken.threeDSMessageVersion == null) { val error = "Failed to create 3DS2 Transaction. Missing threeDSMessageVersion inside fingerprintToken." + trackTransactionCreationErrorEvent() emitError(ComponentException(error)) return null } @@ -335,9 +336,11 @@ internal class DefaultAdyen3DS2Delegate( is TransactionResult.Success -> result.transaction } } catch (e: SDKNotInitializedException) { + trackTransactionCreationErrorEvent() emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null } catch (e: SDKRuntimeException) { + trackTransactionCreationErrorEvent() emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null } @@ -584,6 +587,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackTransactionCreationErrorEvent() = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_CREATION) + private fun trackFingerprintHandlingErrorEvent() = trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 9c6aead922..a3edfb3617 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -52,6 +52,7 @@ import com.adyen.threeds2.TransactionResult import com.adyen.threeds2.Warning import com.adyen.threeds2.customization.UiCustomization import com.adyen.threeds2.exception.InvalidInputException +import com.adyen.threeds2.exception.SDKNotInitializedException import com.adyen.threeds2.exception.SDKRuntimeException import com.adyen.threeds2.parameters.ChallengeParameters import com.adyen.threeds2.parameters.ConfigParameters @@ -79,7 +80,6 @@ import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import java.io.IOException @@ -221,17 +221,32 @@ internal class DefaultAdyen3DS2DelegateTest( } @Test - fun `creating 3ds2 transaction fails, then an exception emitted`() = runTest { - val error = SDKRuntimeException("test", "test", null) - threeDS2Service.createTransactionError = error - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + fun `creating 3ds2 transaction fails and the error is SDKNotInitializedException, then an exception emitted`() = + runTest { + val error = SDKNotInitializedException() + threeDS2Service.createTransactionError = error + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) - val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) - delegate.identifyShopper(Activity(), encodedJson, false) + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) - assertEquals(error, exceptionFlow.latestValue.cause) - } + assertEquals(error, exceptionFlow.latestValue.cause) + } + + @Test + fun `creating 3ds2 transaction fails and the error is SDKRuntimeException, then an exception emitted`() = + runTest { + val error = SDKRuntimeException("test", "test", null) + threeDS2Service.createTransactionError = error + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + assertEquals(error, exceptionFlow.latestValue.cause) + } @Test fun `creating 3ds2 transaction return transaction error, then details are emitted`() = runTest { @@ -590,7 +605,7 @@ internal class DefaultAdyen3DS2DelegateTest( val expectedEvent = ThreeDS2Events.threeDS2Fingerprint( subType = ThreeDS2Events.SubType.FINGERPRINT_DATA_SENT, ) - analyticsManager.assertLastEventEquals(expectedEvent) + analyticsManager.assertHasEventEquals(expectedEvent) } @ParameterizedTest @@ -649,6 +664,60 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when creating 3ds2 transaction fails and the threeDSMessageVersion is null, then error event is tracked`() = + runTest { + val fingerprintTokenWithoutMessageVersion = + """ + { + "directoryServerId":"id", + "directoryServerPublicKey":"key", + "directoryServerRootCertificates":"certs", + } + """.trimIndent() + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val encodedJson = Base64.encode(fingerprintTokenWithoutMessageVersion.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when creating 3ds2 transaction fails and error is SDKNotInitializedException, then error event is tracked`() = + runTest { + val error = SDKNotInitializedException() + threeDS2Service.createTransactionError = error + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when creating 3ds2 transaction fails and error is SDKRuntimeException, then error event is tracked`() = + runTest { + val error = SDKRuntimeException("test", "test", null) + threeDS2Service.createTransactionError = error + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when fingerprint is submitted automatically and it fails, then error event is tracked`() = runTest { threeDS2Service.transactionResult = From 059d9fa511e5ea51589a8023ae0629c494b768d9 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 4 Dec 2024 10:37:18 +0100 Subject: [PATCH 10/95] Track error events when fingerprint creation fails COAND-1010 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 6 ++ .../ui/DefaultAdyen3DS2DelegateTest.kt | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index abf457bef8..296f80ee9c 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -235,12 +235,14 @@ internal class DefaultAdyen3DS2Delegate( } val configParameters = createAdyenConfigParameters(fingerprintToken) ?: run { + trackFingerprintCreationErrorEvent() emitError(ComponentException("Failed to create ConfigParameters.")) return } val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> adyenLog(AdyenLogLevel.ERROR, throwable) { "Unexpected uncaught 3DS2 Exception" } + trackFingerprintCreationErrorEvent() emitError(CheckoutException("Unexpected 3DS2 exception.", throwable)) } @@ -262,6 +264,7 @@ internal class DefaultAdyen3DS2Delegate( val authenticationRequestParameters = currentTransaction?.authenticationRequestParameters if (authenticationRequestParameters == null) { + trackFingerprintCreationErrorEvent() emitError(ComponentException("Failed to retrieve 3DS2 authentication parameters")) return@launch } @@ -587,6 +590,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackFingerprintCreationErrorEvent() = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_CREATION) + private fun trackTransactionCreationErrorEvent() = trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_CREATION) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index a3edfb3617..8a2f64c542 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -191,6 +191,23 @@ internal class DefaultAdyen3DS2DelegateTest( assertTrue(exceptionFlow.latestValue is ComponentException) } + @Test + fun `when fingerprintToken is partial, then an exception is emitted`() = runTest { + val partialFingerprintToken = + """ + { + "directoryServerId":"id", + } + """.trimIndent() + delegate.initialize(this) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + val encodedJson = Base64.encode(partialFingerprintToken.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + assertTrue(exceptionFlow.latestValue is ComponentException) + } + @Test fun `3ds2 sdk throws an exception while initializing, then an exception emitted`() = runTest { val error = InvalidInputException("test", null) @@ -664,6 +681,52 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when fingerprintToken is partial, then error event is tracked`() = runTest { + val partialFingerprintToken = + """ + { + "directoryServerId":"id", + } + """.trimIndent() + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher(testScheduler))) + + val encodedJson = Base64.encode(partialFingerprintToken.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when 3ds2 sdk throws an exception while initializing, then error event is tracked`() = runTest { + threeDS2Service.initializeError = InvalidInputException("test", null) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher(testScheduler))) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when transaction parameters are null, then error event is tracked`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val encodedJson = Base64.encode(TEST_FINGERPRINT_TOKEN.toByteArray()) + delegate.identifyShopper(Activity(), encodedJson, false) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when creating 3ds2 transaction fails and the threeDSMessageVersion is null, then error event is tracked`() = runTest { From 30dec0eede67178a035e9da6979baa7866a6dfa7 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 4 Dec 2024 14:52:01 +0100 Subject: [PATCH 11/95] Track error events when token is missing fails COAND-1010 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 24 ++++++++-- .../ui/DefaultAdyen3DS2DelegateTest.kt | 47 +++++++++++++++++++ .../core/internal/analytics/ErrorEvent.kt | 1 + 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 296f80ee9c..13963978a8 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -148,6 +148,7 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { + trackFingerprintTokenMissingErrorEvent() emitError(ComponentException("Fingerprint token not found.")) return } @@ -166,6 +167,7 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { + trackChallengeTokenMissingErrorEvent() emitError(ComponentException("Challenge token not found.")) return } @@ -179,10 +181,6 @@ internal class DefaultAdyen3DS2Delegate( action: Threeds2Action, activity: Activity, ) { - if (action.token.isNullOrEmpty()) { - emitError(ComponentException("3DS2 token not found.")) - return - } if (action.subtype == null) { emitError(ComponentException("3DS2 Action subtype not found.")) return @@ -196,7 +194,17 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, subtype: Threeds2Action.SubType, ) { - val token = action.token.orEmpty() + val token = action.token + if (token.isNullOrEmpty()) { + when (subtype) { + Threeds2Action.SubType.FINGERPRINT -> trackFingerprintTokenMissingErrorEvent() + Threeds2Action.SubType.CHALLENGE -> trackChallengeTokenMissingErrorEvent() + } + + emitError(ComponentException("3DS2 token not found.")) + return + } + when (subtype) { Threeds2Action.SubType.FINGERPRINT -> { trackFingerprintActionEvent(action) @@ -590,6 +598,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackFingerprintTokenMissingErrorEvent() = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING) + private fun trackFingerprintCreationErrorEvent() = trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_CREATION) @@ -607,6 +618,9 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } + private fun trackChallengeTokenMissingErrorEvent() = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING) + private fun trackChallengeTransactionMissingErrorEvent() = trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 8a2f64c542..c99f4bb997 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -681,6 +681,53 @@ internal class DefaultAdyen3DS2DelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } + @Test + fun `when action is Threeds2FingerprintAction and token is null, then error event is tracked`() = runTest { + delegate.initialize(this) + + delegate.handleAction(Threeds2FingerprintAction(token = null), Activity()) + + val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TOKEN_MISSING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when action is Threeds2ChallengeAction and token is null, then error event is tracked`() = runTest { + delegate.initialize(this) + + delegate.handleAction(Threeds2ChallengeAction(token = null), Activity()) + + val expectedEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_TOKEN_MISSING, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when action is Threeds2Action and token is null, then error event is tracked`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher(testScheduler))) + + delegate.handleAction( + Threeds2Action(token = null, subtype = Threeds2Action.SubType.FINGERPRINT.value), + Activity(), + ) + val expectedFingerprintEvent = ThreeDS2Events.threeDS2FingerprintError( + event = ErrorEvent.THREEDS2_TOKEN_MISSING, + ) + analyticsManager.assertLastEventEquals(expectedFingerprintEvent) + + delegate.handleAction( + Threeds2Action(token = null, subtype = Threeds2Action.SubType.CHALLENGE.value), + Activity(), + ) + val expectedChallengeEvent = ThreeDS2Events.threeDS2ChallengeError( + event = ErrorEvent.THREEDS2_TOKEN_MISSING, + ) + analyticsManager.assertLastEventEquals(expectedChallengeEvent) + } + @Test fun `when fingerprintToken is partial, then error event is tracked`() = runTest { val partialFingerprintToken = diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt index 8832aa083c..cee3cfd2fe 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt @@ -33,6 +33,7 @@ enum class ErrorEvent(val errorType: Type, val errorCode: String) { API_NATIVE_REDIRECT(Type.API_ERROR, "625"), // 3DS2 + THREEDS2_TOKEN_MISSING(Type.THREEDS2, "701"), THREEDS2_TOKEN_DECODING(Type.THREEDS2, "704"), THREEDS2_FINGERPRINT_CREATION(Type.THREEDS2, "705"), THREEDS2_TRANSACTION_CREATION(Type.THREEDS2, "706"), From f36ca3397b8fef713dc671b98562d659a9d93c01 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 10 Dec 2024 13:38:58 +0100 Subject: [PATCH 12/95] Add error messages for 3ds2 errors which are being tracked at least once COAND-1010 --- .../internal/analytics/ThreeDS2Events.kt | 12 +- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 128 ++++++++++++------ .../ui/DefaultAdyen3DS2DelegateTest.kt | 2 +- .../core/internal/analytics/GenericEvents.kt | 2 + .../analytics/TestAnalyticsManager.kt | 2 + 5 files changed, 99 insertions(+), 47 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt index 3980183604..e83d5eec24 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/analytics/ThreeDS2Events.kt @@ -42,14 +42,22 @@ internal object ThreeDS2Events { message = message, ) - fun threeDS2FingerprintError(event: ErrorEvent) = GenericEvents.error( + fun threeDS2FingerprintError( + event: ErrorEvent, + message: String? = null + ) = GenericEvents.error( component = "threeDS2Fingerprint", event = event, + message = message, ) - fun threeDS2ChallengeError(event: ErrorEvent) = GenericEvents.error( + fun threeDS2ChallengeError( + event: ErrorEvent, + message: String? = null + ) = GenericEvents.error( component = "threeDS2Challenge", event = event, + message = message, ) enum class SubType(val value: String) { diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 13963978a8..a5c141fefc 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -148,7 +148,7 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { - trackFingerprintTokenMissingErrorEvent() + trackFingerprintTokenMissingErrorEvent("Token is missing for Threeds2FingerprintAction") emitError(ComponentException("Fingerprint token not found.")) return } @@ -167,7 +167,7 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { - trackChallengeTokenMissingErrorEvent() + trackChallengeTokenMissingErrorEvent("Token is missing for Threeds2ChallengeAction") emitError(ComponentException("Challenge token not found.")) return } @@ -196,9 +196,15 @@ internal class DefaultAdyen3DS2Delegate( ) { val token = action.token if (token.isNullOrEmpty()) { + val errorMessage = "Token is missing for Threeds2Action" when (subtype) { - Threeds2Action.SubType.FINGERPRINT -> trackFingerprintTokenMissingErrorEvent() - Threeds2Action.SubType.CHALLENGE -> trackChallengeTokenMissingErrorEvent() + Threeds2Action.SubType.FINGERPRINT -> trackFingerprintTokenMissingErrorEvent( + errorMessage + ) + + Threeds2Action.SubType.CHALLENGE -> trackChallengeTokenMissingErrorEvent( + errorMessage + ) } emitError(ComponentException("3DS2 token not found.")) @@ -224,6 +230,7 @@ internal class DefaultAdyen3DS2Delegate( } } + @Suppress("LongMethod") @VisibleForTesting internal fun identifyShopper( activity: Activity, @@ -243,14 +250,18 @@ internal class DefaultAdyen3DS2Delegate( } val configParameters = createAdyenConfigParameters(fingerprintToken) ?: run { - trackFingerprintCreationErrorEvent() + trackFingerprintCreationErrorEvent( + "Fingerprint creation failed because the token is partial" + ) emitError(ComponentException("Failed to create ConfigParameters.")) return } val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> adyenLog(AdyenLogLevel.ERROR, throwable) { "Unexpected uncaught 3DS2 Exception" } - trackFingerprintCreationErrorEvent() + trackFingerprintCreationErrorEvent( + "Fingerprint creation failed because of uncaught exception" + ) emitError(CheckoutException("Unexpected 3DS2 exception.", throwable)) } @@ -260,19 +271,30 @@ internal class DefaultAdyen3DS2Delegate( adyenLog(AdyenLogLevel.DEBUG) { "initialize 3DS2 SDK" } val initializeResult = - threeDS2Service.initialize(activity, configParameters, null, componentParams.uiCustomization) + threeDS2Service.initialize( + activity, + configParameters, + null, + componentParams.uiCustomization + ) if (initializeResult is InitializeResult.Failure) { - val details = makeDetails(initializeResult.transactionStatus, initializeResult.additionalDetails) + val details = makeDetails( + initializeResult.transactionStatus, + initializeResult.additionalDetails + ) emitDetails(details) return@launch } currentTransaction = createTransaction(fingerprintToken) ?: return@launch - val authenticationRequestParameters = currentTransaction?.authenticationRequestParameters + val authenticationRequestParameters = + currentTransaction?.authenticationRequestParameters if (authenticationRequestParameters == null) { - trackFingerprintCreationErrorEvent() + trackFingerprintCreationErrorEvent( + "Fingerprint creation failed because authentication parameters do not exist" + ) emitError(ComponentException("Failed to retrieve 3DS2 authentication parameters")) return@launch } @@ -324,8 +346,11 @@ internal class DefaultAdyen3DS2Delegate( private fun createTransaction(fingerprintToken: FingerprintToken): Transaction? { if (fingerprintToken.threeDSMessageVersion == null) { - val error = "Failed to create 3DS2 Transaction. Missing threeDSMessageVersion inside fingerprintToken." - trackTransactionCreationErrorEvent() + val error = + "Failed to create 3DS2 Transaction. Missing threeDSMessageVersion inside fingerprintToken." + trackTransactionCreationErrorEvent( + "Transaction creation failed because threeDSMessageVersion is missing" + ) emitError(ComponentException(error)) return null } @@ -337,7 +362,9 @@ internal class DefaultAdyen3DS2Delegate( return try { adyenLog(AdyenLogLevel.DEBUG) { "create transaction" } - when (val result = threeDS2Service.createTransaction(null, fingerprintToken.threeDSMessageVersion)) { + val result = + threeDS2Service.createTransaction(null, fingerprintToken.threeDSMessageVersion) + when (result) { is TransactionResult.Failure -> { val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) @@ -347,11 +374,15 @@ internal class DefaultAdyen3DS2Delegate( is TransactionResult.Success -> result.transaction } } catch (e: SDKNotInitializedException) { - trackTransactionCreationErrorEvent() + trackTransactionCreationErrorEvent( + "Transaction creation failed because the SDK is not initialized" + ) emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null } catch (e: SDKRuntimeException) { - trackTransactionCreationErrorEvent() + trackTransactionCreationErrorEvent( + "Transaction creation failed because SDK threw runtime exception" + ) emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null } @@ -480,7 +511,7 @@ internal class DefaultAdyen3DS2Delegate( ) analyticsManager?.trackEvent(challengeDisplayedEvent) } catch (e: InvalidInputException) { - trackChallengeHandlingFailedErrorEvent() + trackChallengeHandlingFailedErrorEvent("Challenge failed because input is invalid") emitError(CheckoutException("Error starting challenge", e)) } } @@ -514,7 +545,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(transactionStatus) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent() + trackChallengeHandlingFailedErrorEvent( + "Challenge completed and details cannot be created" + ) emitError(e) } finally { closeTransaction() @@ -533,7 +566,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent() + trackChallengeHandlingFailedErrorEvent( + "Challenge timed out and details cannot be created" + ) emitError(e) } finally { closeTransaction() @@ -546,7 +581,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent() + trackChallengeHandlingFailedErrorEvent( + "Challenge failed and details cannot be created" + ) emitError(e) } finally { closeTransaction() @@ -585,9 +622,11 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } - private fun trackFingerprintActionEvent(action: Action) = trackActionEvent(action, ANALYTICS_MESSAGE_FINGERPRINT) + private fun trackFingerprintActionEvent(action: Action) = + trackActionEvent(action, ANALYTICS_MESSAGE_FINGERPRINT) - private fun trackChallengeActionEvent(action: Action) = trackActionEvent(action, ANALYTICS_MESSAGE_CHALLENGE) + private fun trackChallengeActionEvent(action: Action) = + trackActionEvent(action, ANALYTICS_MESSAGE_CHALLENGE) private fun trackActionEvent(action: Action, message: String) { val event = GenericEvents.action( @@ -598,40 +637,40 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } - private fun trackFingerprintTokenMissingErrorEvent() = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING) + private fun trackFingerprintTokenMissingErrorEvent(message: String? = null) = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING, message) - private fun trackFingerprintCreationErrorEvent() = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_CREATION) + private fun trackFingerprintCreationErrorEvent(message: String? = null) = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_CREATION, message) - private fun trackTransactionCreationErrorEvent() = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_CREATION) + private fun trackTransactionCreationErrorEvent(message: String? = null) = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_CREATION, message) - private fun trackFingerprintHandlingErrorEvent() = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING) + private fun trackFingerprintHandlingErrorEvent(message: String? = null) = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, message) - private fun trackFingerprintTokenDecodeErrorEvent() = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) + private fun trackFingerprintTokenDecodeErrorEvent(message: String? = null) = + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING, message) - private fun trackFingerprintErrorEvent(errorEvent: ErrorEvent) { - val event = ThreeDS2Events.threeDS2FingerprintError(errorEvent) + private fun trackFingerprintErrorEvent(errorEvent: ErrorEvent, message: String?) { + val event = ThreeDS2Events.threeDS2FingerprintError(errorEvent, message) analyticsManager?.trackEvent(event) } - private fun trackChallengeTokenMissingErrorEvent() = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING) + private fun trackChallengeTokenMissingErrorEvent(message: String? = null) = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING, message) - private fun trackChallengeTransactionMissingErrorEvent() = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING) + private fun trackChallengeTransactionMissingErrorEvent(message: String? = null) = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING, message) - private fun trackChallengeTokenDecodeErrorEvent() = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) + private fun trackChallengeTokenDecodeErrorEvent(message: String? = null) = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING, message) - private fun trackChallengeHandlingFailedErrorEvent() = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_CHALLENGE_HANDLING) + private fun trackChallengeHandlingFailedErrorEvent(message: String? = null) = + trackChallengeErrorEvent(ErrorEvent.THREEDS2_CHALLENGE_HANDLING, message) - private fun trackChallengeErrorEvent(errorEvent: ErrorEvent) { - val event = ThreeDS2Events.threeDS2ChallengeError(errorEvent) + private fun trackChallengeErrorEvent(errorEvent: ErrorEvent, message: String?) { + val event = ThreeDS2Events.threeDS2ChallengeError(errorEvent, message) analyticsManager?.trackEvent(event) } @@ -702,7 +741,8 @@ internal class DefaultAdyen3DS2Delegate( companion object { @VisibleForTesting - internal const val ANALYTICS_MESSAGE_FINGERPRINT = "Fingerprint action was handled by the SDK" + internal const val ANALYTICS_MESSAGE_FINGERPRINT = + "Fingerprint action was handled by the SDK" @VisibleForTesting internal const val ANALYTICS_MESSAGE_CHALLENGE = "Challenge action was handled by the SDK" diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index c99f4bb997..ee00b4ed00 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -688,7 +688,7 @@ internal class DefaultAdyen3DS2DelegateTest( delegate.handleAction(Threeds2FingerprintAction(token = null), Activity()) val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( - event = ErrorEvent.THREEDS2_TOKEN_MISSING, + event = ErrorEvent.THREEDS2_TOKEN_MISSING ) analyticsManager.assertLastEventEquals(expectedEvent) } diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt index d6d3c4d561..1bfbd4dc6a 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt @@ -129,10 +129,12 @@ object GenericEvents { component: String, event: ErrorEvent, target: String? = null, + message: String? = null, ) = AnalyticsEvent.Error( component = component, errorType = event.errorType, code = event.errorCode, target = target, + message = message, ) } diff --git a/components-core/src/testFixtures/java/com/adyen/checkout/components/core/internal/analytics/TestAnalyticsManager.kt b/components-core/src/testFixtures/java/com/adyen/checkout/components/core/internal/analytics/TestAnalyticsManager.kt index f04b237a7b..8ae16c9bf2 100644 --- a/components-core/src/testFixtures/java/com/adyen/checkout/components/core/internal/analytics/TestAnalyticsManager.kt +++ b/components-core/src/testFixtures/java/com/adyen/checkout/components/core/internal/analytics/TestAnalyticsManager.kt @@ -54,6 +54,8 @@ class TestAnalyticsManager : AnalyticsManager { // Exclude these fields as they are generated at runtime "id", "timestamp", + // Exclude message field as it is not required + "message", ) return re.matches(actual) } From 1386c37db3ece1c0bbea7f43820ceb462ff8be81 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 10 Dec 2024 13:57:53 +0100 Subject: [PATCH 13/95] Clean up old tests where we check the message for analytics events COAND-1010 --- .../adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt | 11 ++--------- .../internal/ui/DefaultAdyen3DS2DelegateTest.kt | 4 ---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index a5c141fefc..9771ac0cf8 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -623,10 +623,10 @@ internal class DefaultAdyen3DS2Delegate( } private fun trackFingerprintActionEvent(action: Action) = - trackActionEvent(action, ANALYTICS_MESSAGE_FINGERPRINT) + trackActionEvent(action, "Fingerprint action was handled by the SDK") private fun trackChallengeActionEvent(action: Action) = - trackActionEvent(action, ANALYTICS_MESSAGE_CHALLENGE) + trackActionEvent(action, "Challenge action was handled by the SDK") private fun trackActionEvent(action: Action, message: String) { val event = GenericEvents.action( @@ -740,13 +740,6 @@ internal class DefaultAdyen3DS2Delegate( } companion object { - @VisibleForTesting - internal const val ANALYTICS_MESSAGE_FINGERPRINT = - "Fingerprint action was handled by the SDK" - - @VisibleForTesting - internal const val ANALYTICS_MESSAGE_CHALLENGE = "Challenge action was handled by the SDK" - private const val DEFAULT_CHALLENGE_TIME_OUT = 10 private const val PROTOCOL_VERSION_2_1_0 = "2.1.0" diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index ee00b4ed00..df21e5c214 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -548,7 +548,6 @@ internal class DefaultAdyen3DS2DelegateTest( val expectedEvent = GenericEvents.action( component = TEST_PAYMENT_METHOD_TYPE, subType = TEST_ACTION_TYPE, - message = DefaultAdyen3DS2Delegate.ANALYTICS_MESSAGE_FINGERPRINT, ) analyticsManager.assertHasEventEquals(expectedEvent) } @@ -567,7 +566,6 @@ internal class DefaultAdyen3DS2DelegateTest( val expectedEvent = GenericEvents.action( component = TEST_PAYMENT_METHOD_TYPE, subType = TEST_ACTION_TYPE, - message = DefaultAdyen3DS2Delegate.ANALYTICS_MESSAGE_CHALLENGE, ) analyticsManager.assertHasEventEquals(expectedEvent) } @@ -587,7 +585,6 @@ internal class DefaultAdyen3DS2DelegateTest( val expectedEvent = GenericEvents.action( component = TEST_PAYMENT_METHOD_TYPE, subType = TEST_ACTION_TYPE, - message = DefaultAdyen3DS2Delegate.ANALYTICS_MESSAGE_FINGERPRINT, ) analyticsManager.assertHasEventEquals(expectedEvent) } @@ -607,7 +604,6 @@ internal class DefaultAdyen3DS2DelegateTest( val expectedEvent = GenericEvents.action( component = TEST_PAYMENT_METHOD_TYPE, subType = TEST_ACTION_TYPE, - message = DefaultAdyen3DS2Delegate.ANALYTICS_MESSAGE_CHALLENGE, ) analyticsManager.assertHasEventEquals(expectedEvent) } From bd7e807bfcbd4682b88d129e12f4e749d63c6138 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 11 Dec 2024 10:53:52 +0100 Subject: [PATCH 14/95] Optimize tracking of error events for 3ds2 COAND-1010 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 118 +++++++++--------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 9771ac0cf8..840f4b8094 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -148,7 +148,10 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { - trackFingerprintTokenMissingErrorEvent("Token is missing for Threeds2FingerprintAction") + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TOKEN_MISSING, + message = "Token is missing for Threeds2FingerprintAction", + ) emitError(ComponentException("Fingerprint token not found.")) return } @@ -167,7 +170,10 @@ internal class DefaultAdyen3DS2Delegate( activity: Activity, ) { if (action.token.isNullOrEmpty()) { - trackChallengeTokenMissingErrorEvent("Token is missing for Threeds2ChallengeAction") + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TOKEN_MISSING, + message = "Token is missing for Threeds2ChallengeAction", + ) emitError(ComponentException("Challenge token not found.")) return } @@ -198,12 +204,14 @@ internal class DefaultAdyen3DS2Delegate( if (token.isNullOrEmpty()) { val errorMessage = "Token is missing for Threeds2Action" when (subtype) { - Threeds2Action.SubType.FINGERPRINT -> trackFingerprintTokenMissingErrorEvent( - errorMessage + Threeds2Action.SubType.FINGERPRINT -> trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TOKEN_MISSING, + message = errorMessage, ) - Threeds2Action.SubType.CHALLENGE -> trackChallengeTokenMissingErrorEvent( - errorMessage + Threeds2Action.SubType.CHALLENGE -> trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TOKEN_MISSING, + message = errorMessage, ) } @@ -244,14 +252,15 @@ internal class DefaultAdyen3DS2Delegate( val fingerprintToken = try { decodeFingerprintToken(encodedFingerprintToken) } catch (e: CheckoutException) { - trackFingerprintTokenDecodeErrorEvent() + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) emitError(ComponentException("Failed to decode fingerprint token", e)) return } val configParameters = createAdyenConfigParameters(fingerprintToken) ?: run { - trackFingerprintCreationErrorEvent( - "Fingerprint creation failed because the token is partial" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + message = "Fingerprint creation failed because the token is partial", ) emitError(ComponentException("Failed to create ConfigParameters.")) return @@ -259,8 +268,9 @@ internal class DefaultAdyen3DS2Delegate( val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> adyenLog(AdyenLogLevel.ERROR, throwable) { "Unexpected uncaught 3DS2 Exception" } - trackFingerprintCreationErrorEvent( - "Fingerprint creation failed because of uncaught exception" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + message = "Fingerprint creation failed because of uncaught exception", ) emitError(CheckoutException("Unexpected 3DS2 exception.", throwable)) } @@ -275,13 +285,13 @@ internal class DefaultAdyen3DS2Delegate( activity, configParameters, null, - componentParams.uiCustomization + componentParams.uiCustomization, ) if (initializeResult is InitializeResult.Failure) { val details = makeDetails( initializeResult.transactionStatus, - initializeResult.additionalDetails + initializeResult.additionalDetails, ) emitDetails(details) return@launch @@ -292,8 +302,9 @@ internal class DefaultAdyen3DS2Delegate( val authenticationRequestParameters = currentTransaction?.authenticationRequestParameters if (authenticationRequestParameters == null) { - trackFingerprintCreationErrorEvent( - "Fingerprint creation failed because authentication parameters do not exist" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + message = "Fingerprint creation failed because authentication parameters do not exist", ) emitError(ComponentException("Failed to retrieve 3DS2 authentication parameters")) return@launch @@ -346,12 +357,14 @@ internal class DefaultAdyen3DS2Delegate( private fun createTransaction(fingerprintToken: FingerprintToken): Transaction? { if (fingerprintToken.threeDSMessageVersion == null) { - val error = - "Failed to create 3DS2 Transaction. Missing threeDSMessageVersion inside fingerprintToken." - trackTransactionCreationErrorEvent( - "Transaction creation failed because threeDSMessageVersion is missing" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + message = "Transaction creation failed because threeDSMessageVersion is missing", ) + + val error = "Failed to create 3DS2 Transaction. Missing threeDSMessageVersion inside fingerprintToken." emitError(ComponentException(error)) + return null } @@ -374,14 +387,16 @@ internal class DefaultAdyen3DS2Delegate( is TransactionResult.Success -> result.transaction } } catch (e: SDKNotInitializedException) { - trackTransactionCreationErrorEvent( - "Transaction creation failed because the SDK is not initialized" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + message = "Transaction creation failed because the SDK is not initialized", ) emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null } catch (e: SDKRuntimeException) { - trackTransactionCreationErrorEvent( - "Transaction creation failed because SDK threw runtime exception" + trackFingerprintErrorEvent( + errorEvent = ErrorEvent.THREEDS2_TRANSACTION_CREATION, + message = "Transaction creation failed because SDK threw runtime exception", ) emitError(ComponentException("Failed to create 3DS2 Transaction", e)) null @@ -421,7 +436,7 @@ internal class DefaultAdyen3DS2Delegate( .fold( onSuccess = { result -> onSubmitFingerprintResult(result, activity) }, onFailure = { e -> - trackFingerprintHandlingErrorEvent() + trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING) emitError(ComponentException("Unable to submit fingerprint", e)) }, ) @@ -475,7 +490,7 @@ internal class DefaultAdyen3DS2Delegate( adyenLog(AdyenLogLevel.DEBUG) { "challengeShopper" } if (currentTransaction == null) { - trackChallengeTransactionMissingErrorEvent() + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING) emitError( Authentication3DS2Exception("Failed to make challenge, missing reference to initial transaction."), ) @@ -486,7 +501,7 @@ internal class DefaultAdyen3DS2Delegate( val challengeTokenJson: JSONObject = try { JSONObject(decodedChallengeToken) } catch (e: JSONException) { - trackChallengeTokenDecodeErrorEvent() + trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING) emitError(ComponentException("JSON parsing of challenge token failed", e)) return } @@ -511,7 +526,10 @@ internal class DefaultAdyen3DS2Delegate( ) analyticsManager?.trackEvent(challengeDisplayedEvent) } catch (e: InvalidInputException) { - trackChallengeHandlingFailedErrorEvent("Challenge failed because input is invalid") + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge failed because input is invalid", + ) emitError(CheckoutException("Error starting challenge", e)) } } @@ -545,8 +563,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(transactionStatus) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent( - "Challenge completed and details cannot be created" + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge completed and details cannot be created", ) emitError(e) } finally { @@ -566,8 +585,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent( - "Challenge timed out and details cannot be created" + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge timed out and details cannot be created", ) emitError(e) } finally { @@ -581,8 +601,9 @@ internal class DefaultAdyen3DS2Delegate( val details = makeDetails(result.transactionStatus, result.additionalDetails) emitDetails(details) } catch (e: CheckoutException) { - trackChallengeHandlingFailedErrorEvent( - "Challenge failed and details cannot be created" + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge failed and details cannot be created", ) emitError(e) } finally { @@ -637,39 +658,12 @@ internal class DefaultAdyen3DS2Delegate( analyticsManager?.trackEvent(event) } - private fun trackFingerprintTokenMissingErrorEvent(message: String? = null) = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING, message) - - private fun trackFingerprintCreationErrorEvent(message: String? = null) = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_CREATION, message) - - private fun trackTransactionCreationErrorEvent(message: String? = null) = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_CREATION, message) - - private fun trackFingerprintHandlingErrorEvent(message: String? = null) = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, message) - - private fun trackFingerprintTokenDecodeErrorEvent(message: String? = null) = - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING, message) - - private fun trackFingerprintErrorEvent(errorEvent: ErrorEvent, message: String?) { + private fun trackFingerprintErrorEvent(errorEvent: ErrorEvent, message: String? = null) { val event = ThreeDS2Events.threeDS2FingerprintError(errorEvent, message) analyticsManager?.trackEvent(event) } - private fun trackChallengeTokenMissingErrorEvent(message: String? = null) = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_MISSING, message) - - private fun trackChallengeTransactionMissingErrorEvent(message: String? = null) = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TRANSACTION_MISSING, message) - - private fun trackChallengeTokenDecodeErrorEvent(message: String? = null) = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_TOKEN_DECODING, message) - - private fun trackChallengeHandlingFailedErrorEvent(message: String? = null) = - trackChallengeErrorEvent(ErrorEvent.THREEDS2_CHALLENGE_HANDLING, message) - - private fun trackChallengeErrorEvent(errorEvent: ErrorEvent, message: String?) { + private fun trackChallengeErrorEvent(errorEvent: ErrorEvent, message: String? = null) { val event = ThreeDS2Events.threeDS2ChallengeError(errorEvent, message) analyticsManager?.trackEvent(event) } From 82d0b373b4188d6722c08ab936f1bd49e5931af5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:08:48 +0000 Subject: [PATCH 15/95] Update lifecycle to v2.8.7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c148c8d24..4f637f3a37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ autofill = "1.3.0-beta01" browser = "1.8.0" coroutines = "1.9.0" fragment = "1.8.5" -lifecycle = "2.8.6" +lifecycle = "2.8.7" material = "1.12.0" recyclerview = "1.3.2" constraintlayout = "2.2.0" From ac82a6f9ccbcdcd700b4fce34516417da60c5511 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:30:09 +0000 Subject: [PATCH 16/95] Update dependency androidx.compose:compose-bom to v2024.11.00 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 308 +++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f637f3a37..8e48826fde 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ constraintlayout = "2.2.0" # Compose dependencies compose-activity = "1.9.3" -compose-bom = "2024.10.00" +compose-bom = "2024.11.00" compose-hilt = "1.2.0" compose-viewmodel = "2.8.7" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 76d39069ea..cbf5b86ade 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -700,6 +700,11 @@ + + + + + @@ -748,6 +753,14 @@ + + + + + + + + @@ -796,6 +809,14 @@ + + + + + + + + @@ -844,6 +865,14 @@ + + + + + + + + @@ -892,6 +921,14 @@ + + + + + + + + @@ -980,6 +1017,14 @@ + + + + + + + + @@ -1028,6 +1073,14 @@ + + + + + + + + @@ -1076,6 +1129,14 @@ + + + + + + + + @@ -1124,6 +1185,14 @@ + + + + + + + + @@ -1154,6 +1223,11 @@ + + + + + @@ -1202,6 +1276,14 @@ + + + + + + + + @@ -1250,6 +1332,14 @@ + + + + + + + + @@ -1298,6 +1388,14 @@ + + + + + + + + @@ -1346,6 +1444,14 @@ + + + + + + + + @@ -1394,6 +1500,14 @@ + + + + + + + + @@ -1418,6 +1532,14 @@ + + + + + + + + @@ -1442,6 +1564,14 @@ + + + + + + + + @@ -1495,6 +1625,14 @@ + + + + + + + + @@ -1543,6 +1681,14 @@ + + + + + + + + @@ -1591,6 +1737,14 @@ + + + + + + + + @@ -1639,6 +1793,14 @@ + + + + + + + + @@ -1687,6 +1849,14 @@ + + + + + + + + @@ -1735,6 +1905,14 @@ + + + + + + + + @@ -1783,6 +1961,14 @@ + + + + + + + + @@ -1831,6 +2017,14 @@ + + + + + + + + @@ -1879,6 +2073,14 @@ + + + + + + + + @@ -1927,6 +2129,14 @@ + + + + + + + + @@ -1975,6 +2185,14 @@ + + + + + + + + @@ -2023,6 +2241,14 @@ + + + + + + + + @@ -2071,6 +2297,14 @@ + + + + + + + + @@ -2101,6 +2335,11 @@ + + + + + @@ -2149,6 +2388,14 @@ + + + + + + + + @@ -2179,6 +2426,11 @@ + + + + + @@ -2227,6 +2479,14 @@ + + + + + + + + @@ -2275,6 +2535,14 @@ + + + + + + + + @@ -2323,6 +2591,14 @@ + + + + + + + + @@ -2371,6 +2647,14 @@ + + + + + + + + @@ -2419,6 +2703,14 @@ + + + + + + + + @@ -2467,6 +2759,14 @@ + + + + + + + + @@ -2515,6 +2815,14 @@ + + + + + + + + From 59d3e13a5b8142872bf5b66477010f655bed0aee Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 14 May 2024 18:47:29 +0200 Subject: [PATCH 17/95] Add play services coroutines COAND-855 --- googlepay/build.gradle | 1 + gradle/libs.versions.toml | 1 + gradle/verification-metadata.xml | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/googlepay/build.gradle b/googlepay/build.gradle index ecd8f4e789..c4596240a1 100644 --- a/googlepay/build.gradle +++ b/googlepay/build.gradle @@ -40,6 +40,7 @@ dependencies { api project(':sessions-core') // Dependencies + implementation libs.google.pay.play.services.coroutines api libs.google.pay.play.services.wallet //Tests diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e48826fde..da5c5691fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,6 +108,7 @@ compose-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel- # this unused dependency is needed so that renovate can update the compose compiler version. More info in: https://github.com/renovatebot/renovate/issues/18354 compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compose-compiler" } google-pay-compose-button = { group = "com.google.pay.button", name = "compose-pay-button", version.ref = "google-pay-compose-button" } +google-pay-play-services-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-play-services", version.ref = "coroutines" } google-pay-play-services-wallet = { group = "com.google.android.gms", name = "play-services-wallet", version.ref = "play-services-wallet" } hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index cbf5b86ade..213fccdb8c 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -16453,6 +16453,14 @@ + + + + + + + + From 516fe6a6423d7669ad15e7933394a712741b973a Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 16:15:27 +0200 Subject: [PATCH 18/95] Setup wrapper view and fragment for Google Pay COAND-855 --- googlepay/build.gradle | 4 ++ .../internal/ui/DefaultGooglePayDelegate.kt | 3 ++ .../internal/ui/GooglePayDelegate.kt | 5 +- .../internal/ui/GooglePayFragment.kt | 40 +++++++++++++++ .../googlepay/internal/ui/GooglePayView.kt | 51 +++++++++++++++++++ .../internal/ui/GooglePayViewProvider.kt | 38 ++++++++++++++ .../main/res/layout/fragment_google_pay.xml | 17 +++++++ .../src/main/res/layout/view_google_pay.xml | 23 +++++++++ 8 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt create mode 100644 googlepay/src/main/res/layout/fragment_google_pay.xml create mode 100644 googlepay/src/main/res/layout/view_google_pay.xml diff --git a/googlepay/build.gradle b/googlepay/build.gradle index c4596240a1..60ca3a4987 100644 --- a/googlepay/build.gradle +++ b/googlepay/build.gradle @@ -30,6 +30,10 @@ android { testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' consumerProguardFiles "consumer-rules.pro" } + + buildFeatures { + viewBinding true + } } dependencies { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index cf9ceb154d..909e4b2537 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -32,6 +32,7 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData import com.google.android.gms.wallet.Wallet @@ -61,6 +62,8 @@ internal class DefaultGooglePayDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + override fun initialize(coroutineScope: CoroutineScope) { initializeAnalytics(coroutineScope) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 2d393b6d19..0ef114151e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -14,9 +14,12 @@ import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface GooglePayDelegate : PaymentComponentDelegate { +internal interface GooglePayDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { val componentStateFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt new file mode 100644 index 0000000000..478ad167aa --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding + +internal class GooglePayFragment : Fragment() { + + private var _binding: FragmentGooglePayBinding? = null + private val binding: FragmentGooglePayBinding get() = requireNotNull(_binding) + + private var _delegate: GooglePayDelegate? = null + private val delegate: GooglePayDelegate get() = requireNotNull(_delegate) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = FragmentGooglePayBinding.inflate(inflater, container, false) + return binding.root + } + + fun initialize(delegate: GooglePayDelegate) { + _delegate = delegate + } + + override fun onDestroyView() { + _delegate = null + _binding = null + super.onDestroyView() + } +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt new file mode 100644 index 0000000000..b9972cabe9 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.googlepay.databinding.ViewGooglePayBinding +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import kotlinx.coroutines.CoroutineScope + +internal class GooglePayView internal constructor( + layoutInflater: LayoutInflater, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(layoutInflater.context, attrs, defStyleAttr), ComponentView { + + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 + ) : this(LayoutInflater.from(context), attrs, defStyleAttr) + + private var binding = ViewGooglePayBinding.inflate(layoutInflater, this) + + private var delegate: GooglePayDelegate? = null + + override fun initView(delegate: ComponentDelegate, coroutineScope: CoroutineScope, localizedContext: Context) { + require(delegate is GooglePayDelegate) { "Unsupported delegate type" } + this.delegate = delegate + initializeFragment(delegate) + } + + private fun initializeFragment(delegate: GooglePayDelegate) { + binding.fragmentContainer.getFragment()?.initialize(delegate) + } + + override fun highlightValidationErrors() = Unit + + override fun getView(): View = this +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt new file mode 100644 index 0000000000..1ca8a11d61 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.view.LayoutInflater +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewProvider + +internal object GooglePayViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context, + ): ComponentView = when (viewType) { + GooglePayComponentViewType -> GooglePayView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } + + override fun getView( + viewType: ComponentViewType, + layoutInflater: LayoutInflater + ): ComponentView = when (viewType) { + GooglePayComponentViewType -> GooglePayView(layoutInflater) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object GooglePayComponentViewType : ComponentViewType { + override val viewProvider: ViewProvider = GooglePayViewProvider +} diff --git a/googlepay/src/main/res/layout/fragment_google_pay.xml b/googlepay/src/main/res/layout/fragment_google_pay.xml new file mode 100644 index 0000000000..1e20bfdcc9 --- /dev/null +++ b/googlepay/src/main/res/layout/fragment_google_pay.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/googlepay/src/main/res/layout/view_google_pay.xml b/googlepay/src/main/res/layout/view_google_pay.xml new file mode 100644 index 0000000000..ce06d9bc88 --- /dev/null +++ b/googlepay/src/main/res/layout/view_google_pay.xml @@ -0,0 +1,23 @@ + + + + + + + + + From fde6145de782a68f6bdf3a5a9782b8ce78f31e4f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:20:33 +0200 Subject: [PATCH 19/95] Implement new Google Pay flow COAND-855 --- .../checkout/googlepay/GooglePayComponent.kt | 14 +++- .../provider/GooglePayComponentProvider.kt | 2 + .../internal/ui/DefaultGooglePayDelegate.kt | 65 +++++++++++++++++++ .../internal/ui/GooglePayDelegate.kt | 10 +++ .../internal/ui/GooglePayFragment.kt | 13 ++-- .../googlepay/internal/util/TaskExtensions.kt | 39 +++++++++++ 6 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index f484ff2b2b..dc14b90952 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -28,6 +28,7 @@ import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewableComponent +import com.adyen.checkout.ui.core.internal.util.mergeViewFlows import kotlinx.coroutines.flow.Flow /** @@ -47,7 +48,11 @@ class GooglePayComponent internal constructor( override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + googlePayDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { googlePayDelegate.initialize(viewModelScope) @@ -78,6 +83,13 @@ class GooglePayComponent internal constructor( googlePayDelegate.startGooglePayScreen(activity, requestCode) } + /** + * Start the GooglePay screen. + */ + fun startGooglePayScreen() { + googlePayDelegate.startGooglePayScreen() + } + /** * Returns some of the parameters required to initialize the [Google Pay button](https://docs.adyen.com/payment-methods/google-pay/android-component/#2-show-the-google-pay-button). */ diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index be68f24472..e6e081c479 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -114,6 +114,7 @@ constructor( order = order, componentParams = componentParams, analyticsManager = analyticsManager, + application = application, ) val genericActionDelegate = @@ -201,6 +202,7 @@ constructor( order = checkoutSession.order, componentParams = componentParams, analyticsManager = analyticsManager, + application = application, ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 909e4b2537..d6ad97b5ca 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -9,7 +9,9 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity +import android.app.Application import android.content.Intent +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.components.core.OrderRequest @@ -32,10 +34,14 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.googlepay.internal.util.awaitTask import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData import com.google.android.gms.wallet.Wallet +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -43,6 +49,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch @Suppress("TooManyFunctions") internal class DefaultGooglePayDelegate( @@ -51,6 +58,7 @@ internal class DefaultGooglePayDelegate( private val order: OrderRequest?, override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, + private val application: Application, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -64,7 +72,14 @@ internal class DefaultGooglePayDelegate( override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + private var _coroutineScope: CoroutineScope? = null + private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) + + private lateinit var paymentDataLauncher: ActivityResultLauncher> + override fun initialize(coroutineScope: CoroutineScope) { + _coroutineScope = coroutineScope + initializeAnalytics(coroutineScope) componentStateFlow.onEach { @@ -80,6 +95,10 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } + override fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) { + this.paymentDataLauncher = paymentDataLauncher + } + private fun onState(state: GooglePayComponentState) { if (state.isValid) { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) @@ -148,6 +167,52 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } + override fun startGooglePayScreen() { + adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } + val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) + val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) + coroutineScope.launch { + paymentDataLauncher.launch(paymentDataTask.awaitTask()) + } + } + + override fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) { + when (val statusCode = paymentDataTaskResult.status.statusCode) { + CommonStatusCodes.SUCCESS -> { + val paymentData = paymentDataTaskResult.result + if (paymentData == null) { + adyenLog(AdyenLogLevel.ERROR) { "Result data is null" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + return + } + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } + updateComponentState(paymentData) + } + + CommonStatusCodes.CANCELED -> { + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment canceled" } + exceptionChannel.trySend(ComponentException("GooglePay payment canceled")) + } + + AutoResolveHelper.RESULT_ERROR -> { + val statusMessage: String = paymentDataTaskResult.status.statusMessage?.let { ": $it" }.orEmpty() + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an error$statusMessage" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an error$statusMessage")) + } + + CommonStatusCodes.INTERNAL_ERROR -> { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an internal error" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an internal error")) + } + + else -> { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an unexpected error, statusCode: $statusCode" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + } + } + } + override fun handleActivityResult(resultCode: Int, data: Intent?) { adyenLog(AdyenLogLevel.DEBUG) { "handleActivityResult" } when (resultCode) { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 0ef114151e..0616e26da8 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -10,11 +10,15 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.content.Intent +import androidx.activity.result.ActivityResultLauncher import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate +import com.google.android.gms.tasks.Task +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.flow.Flow internal interface GooglePayDelegate : @@ -27,7 +31,13 @@ internal interface GooglePayDelegate : fun startGooglePayScreen(activity: Activity, requestCode: Int) + fun startGooglePayScreen() + fun handleActivityResult(resultCode: Int, data: Intent?) fun getGooglePayButtonParameters(): GooglePayButtonParameters + + fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) + + fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index 478ad167aa..538df961c9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -14,14 +14,18 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding +import com.google.android.gms.wallet.contract.TaskResultContracts internal class GooglePayFragment : Fragment() { private var _binding: FragmentGooglePayBinding? = null private val binding: FragmentGooglePayBinding get() = requireNotNull(_binding) - private var _delegate: GooglePayDelegate? = null - private val delegate: GooglePayDelegate get() = requireNotNull(_delegate) + private var delegate: GooglePayDelegate? = null + + private val googlePayLauncher = registerForActivityResult(TaskResultContracts.GetPaymentDataResult()) { + delegate?.handlePaymentResult(it) + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentGooglePayBinding.inflate(inflater, container, false) @@ -29,11 +33,12 @@ internal class GooglePayFragment : Fragment() { } fun initialize(delegate: GooglePayDelegate) { - _delegate = delegate + delegate.setPaymentDataLauncher(googlePayLauncher) + this.delegate = delegate } override fun onDestroyView() { - _delegate = null + delegate = null _binding = null super.onDestroyView() } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt new file mode 100644 index 0000000000..d2a843f58d --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.util + +import com.google.android.gms.tasks.CancellationTokenSource +import com.google.android.gms.tasks.Task +import kotlinx.coroutines.suspendCancellableCoroutine +import java.util.concurrent.Executor +import kotlin.coroutines.resume + +internal suspend fun Task.awaitTask(cancellationTokenSource: CancellationTokenSource? = null): Task { + return if (isComplete) { + this + } else { + suspendCancellableCoroutine { cont -> + // Run the callback directly to avoid unnecessarily scheduling on the main thread. + addOnCompleteListener(DirectExecutor, cont::resume) + + cancellationTokenSource?.let { cancellationSource -> + cont.invokeOnCancellation { cancellationSource.cancel() } + } + } + } +} + +/** + * An [Executor] that just directly executes the [Runnable]. + */ +private object DirectExecutor : Executor { + override fun execute(runnable: Runnable) { + runnable.run() + } +} From a29f6733d11ee2e8a263eaf3b7fd58c1bf78fcc0 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:24:55 +0200 Subject: [PATCH 20/95] Implement new flow in examples COAND-855 --- .../ui/googlepay/GooglePayActivityResult.kt | 18 ---------------- .../example/ui/googlepay/GooglePayFragment.kt | 12 +---------- .../compose/SessionsGooglePayActivity.kt | 7 ------- .../compose/SessionsGooglePayScreen.kt | 21 +------------------ .../compose/SessionsGooglePayState.kt | 2 -- .../compose/SessionsGooglePayViewModel.kt | 10 --------- .../checkout/example/ui/main/MainActivity.kt | 10 --------- 7 files changed, 2 insertions(+), 78 deletions(-) delete mode 100644 example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt deleted file mode 100644 index 019178c53e..0000000000 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by josephj on 13/12/2023. - */ - -package com.adyen.checkout.example.ui.googlepay - -import android.content.Intent -import com.adyen.checkout.example.ui.googlepay.compose.SessionsGooglePayComponentData - -internal data class GooglePayActivityResult( - val componentData: SessionsGooglePayComponentData, - val resultCode: Int, - val data: Intent?, -) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index 91609d18a1..4f8feba409 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.example.ui.googlepay -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -148,16 +147,7 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.googlePayButton.initialize(buttonOptions) binding.googlePayButton.setOnClickListener { - googlePayComponent?.startGooglePayScreen(requireActivity(), ACTIVITY_RESULT_CODE) - } - } - - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == ACTIVITY_RESULT_CODE) { - googlePayComponent?.handleActivityResult(resultCode, data) + googlePayComponent?.startGooglePayScreen() } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 2ded1978c8..75e92fe6c6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -59,13 +59,6 @@ class SessionsGooglePayActivity : AppCompatActivity() { } } - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - sessionsGooglePayViewModel.onActivityResult(requestCode, resultCode, data) - } - companion object { internal const val RETURN_URL_EXTRA = "RETURN_URL_EXTRA" } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 41a3e65561..53e9f9147f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalContext import com.adyen.checkout.components.compose.AdyenComponent import com.adyen.checkout.components.compose.get import com.adyen.checkout.example.ui.compose.ResultContent -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult import com.adyen.checkout.googlepay.GooglePayComponent import com.google.pay.button.ButtonTheme import com.google.pay.button.ButtonType @@ -71,7 +70,6 @@ internal fun SessionsGooglePayScreen( with(googlePayState) { HandleStartGooglePay(startGooglePay, viewModel::onGooglePayStarted) - HandleActivityResult(activityResultToHandle, viewModel::onActivityResultHandled) HandleAction(actionToHandle, viewModel::onActionConsumed) HandleNewIntent(intentToHandle, viewModel::onNewIntentHandled) } @@ -126,30 +124,13 @@ private fun HandleStartGooglePay( onGooglePayStarted: () -> Unit ) { if (startGooglePayData == null) return - val activity = LocalContext.current as Activity val googlePayComponent = getGooglePayComponent(componentData = startGooglePayData.componentData) LaunchedEffect(startGooglePayData) { - googlePayComponent.startGooglePayScreen( - activity, - startGooglePayData.requestCode, - ) + googlePayComponent.startGooglePayScreen() onGooglePayStarted() } } -@Composable -private fun HandleActivityResult( - activityResultToHandle: GooglePayActivityResult?, - onActivityResultHandled: () -> Unit -) { - if (activityResultToHandle == null) return - val googlePayComponent = getGooglePayComponent(componentData = activityResultToHandle.componentData) - LaunchedEffect(activityResultToHandle) { - googlePayComponent.handleActivityResult(activityResultToHandle.resultCode, activityResultToHandle.data) - onActivityResultHandled() - } -} - @Composable private fun HandleAction( actionToHandle: SessionsGooglePayAction?, diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt index af6755c00c..94cbb465f6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt @@ -9,13 +9,11 @@ package com.adyen.checkout.example.ui.googlepay.compose import androidx.compose.runtime.Immutable -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult @Immutable internal data class SessionsGooglePayState( val uiState: SessionsGooglePayUIState, val startGooglePay: SessionsStartGooglePayData? = null, - val activityResultToHandle: GooglePayActivityResult? = null, val actionToHandle: SessionsGooglePayAction? = null, val intentToHandle: SessionsGooglePayIntent? = null, ) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index 3939f75ef3..a0f55c55a7 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -28,7 +28,6 @@ import com.adyen.checkout.example.service.getSessionRequest import com.adyen.checkout.example.service.getSettingsInstallmentOptionsMode import com.adyen.checkout.example.ui.compose.ResultState import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.sessions.core.CheckoutSession @@ -199,15 +198,6 @@ internal class SessionsGooglePayViewModel @Inject constructor( updateState { it.copy(actionToHandle = null) } } - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != ACTIVITY_RESULT_CODE) return - updateState { it.copy(activityResultToHandle = GooglePayActivityResult(componentData, resultCode, data)) } - } - - fun onActivityResultHandled() { - updateState { it.copy(activityResultToHandle = null) } - } - fun onNewIntent(intent: Intent) { updateState { it.copy(intentToHandle = SessionsGooglePayIntent(componentData, intent)) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt index 37640c65fd..d4d6101634 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt @@ -223,16 +223,6 @@ class MainActivity : AppCompatActivity() { binding.switchSessions.isChecked = isChecked } - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == GooglePayFragment.ACTIVITY_RESULT_CODE) { - (supportFragmentManager.findFragmentByTag(GooglePayFragment.TAG) as? GooglePayFragment) - ?.onActivityResult(requestCode, resultCode, data) - } - } - override fun onDestroy() { super.onDestroy() componentItemAdapter = null From c2f59ab0c85159038e427ad78a9e6f868cf8e7b8 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:55:31 +0200 Subject: [PATCH 21/95] Improve compose example COAND-855 --- .../compose/SessionsGooglePayActivity.kt | 7 +- .../compose/SessionsGooglePayEvents.kt | 16 +++ .../compose/SessionsGooglePayScreen.kt | 104 ++++++------------ .../compose/SessionsGooglePayState.kt | 19 ++-- .../compose/SessionsGooglePayViewModel.kt | 44 ++------ 5 files changed, 81 insertions(+), 109 deletions(-) create mode 100644 example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 75e92fe6c6..83f46fcd62 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -13,6 +13,8 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.core.view.WindowCompat import com.adyen.checkout.example.ui.theme.ExampleTheme import com.adyen.checkout.example.ui.theme.UIThemeRepository @@ -39,12 +41,15 @@ class SessionsGooglePayActivity : AppCompatActivity() { intent = (intent ?: Intent()).putExtra(RETURN_URL_EXTRA, returnUrl) setContent { + val googlePayState by sessionsGooglePayViewModel.googlePayState.collectAsState() + val eventsState by sessionsGooglePayViewModel.stateEvents.collectAsState() val isDarkTheme = uiThemeRepository.isDarkTheme() ExampleTheme(isDarkTheme) { SessionsGooglePayScreen( useDarkTheme = isDarkTheme, onBackPressed = { onBackPressedDispatcher.onBackPressed() }, - viewModel = sessionsGooglePayViewModel, + googlePayState = googlePayState, + eventsState = eventsState, ) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt new file mode 100644 index 0000000000..45df475e39 --- /dev/null +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/5/2024. + */ + +package com.adyen.checkout.example.ui.googlepay.compose + +internal abstract class SessionsGooglePayEvents internal constructor() { + object None : SessionsGooglePayEvents() + class ComponentData(val data: SessionsGooglePayComponentData) : SessionsGooglePayEvents() + class Action(val action: com.adyen.checkout.components.core.action.Action) : SessionsGooglePayEvents() + class Intent(val intent: android.content.Intent) : SessionsGooglePayEvents() +} diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 53e9f9147f..9f1b59d8ae 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -18,7 +18,7 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -28,8 +28,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -44,8 +42,9 @@ import com.google.pay.button.PayButton @Composable internal fun SessionsGooglePayScreen( useDarkTheme: Boolean, + googlePayState: SessionsGooglePayState, + eventsState: SessionsGooglePayEvents, onBackPressed: () -> Unit, - viewModel: SessionsGooglePayViewModel, ) { Scaffold( modifier = Modifier.windowInsetsPadding(WindowInsets.ime), @@ -54,25 +53,18 @@ internal fun SessionsGooglePayScreen( title = { Text(text = "Google Pay with sessions") }, navigationIcon = { IconButton(onClick = onBackPressed) { - Icon(Icons.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } }, ) }, ) { innerPadding -> - val googlePayState by viewModel.googlePayState.collectAsState() SessionsGooglePayContent( googlePayState = googlePayState, - onButtonClicked = viewModel::onButtonClicked, + googlePayEvents = eventsState, useDarkTheme = useDarkTheme, modifier = Modifier.padding(innerPadding), ) - - with(googlePayState) { - HandleStartGooglePay(startGooglePay, viewModel::onGooglePayStarted) - HandleAction(actionToHandle, viewModel::onActionConsumed) - HandleNewIntent(intentToHandle, viewModel::onNewIntentHandled) - } } } @@ -80,84 +72,58 @@ internal fun SessionsGooglePayScreen( @Composable private fun SessionsGooglePayContent( googlePayState: SessionsGooglePayState, - onButtonClicked: () -> Unit, + googlePayEvents: SessionsGooglePayEvents, useDarkTheme: Boolean, modifier: Modifier = Modifier, ) { + val activity = LocalContext.current as Activity + lateinit var googlePayComponent: GooglePayComponent + + when (googlePayEvents) { + is SessionsGooglePayEvents.ComponentData -> { + googlePayComponent = getGooglePayComponent(componentData = googlePayEvents.data) + } + + is SessionsGooglePayEvents.Action -> { + LaunchedEffect(googlePayEvents.action) { + googlePayComponent.handleAction(googlePayEvents.action, activity) + } + } + + is SessionsGooglePayEvents.Intent -> { + LaunchedEffect(googlePayEvents.intent) { + googlePayComponent.handleIntent(googlePayEvents.intent) + } + } + } + Box( modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { - when (val uiState = googlePayState.uiState) { - SessionsGooglePayUIState.Loading -> { + when (googlePayState) { + SessionsGooglePayState.Loading -> { CircularProgressIndicator() } - is SessionsGooglePayUIState.ShowButton -> { - val googlePayComponent = getGooglePayComponent(componentData = uiState.componentData) + is SessionsGooglePayState.ShowButton -> { + AdyenComponent(googlePayComponent) + PayButton( - onClick = onButtonClicked, + onClick = { googlePayComponent.startGooglePayScreen() }, allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, type = ButtonType.Pay, ) } - is SessionsGooglePayUIState.ShowComponent -> { - val googlePayComponent = getGooglePayComponent(componentData = uiState.componentData) - AdyenComponent( - googlePayComponent, - modifier, - ) - } - - is SessionsGooglePayUIState.FinalResult -> { - ResultContent(uiState.finalResult) + is SessionsGooglePayState.FinalResult -> { + ResultContent(googlePayState.finalResult) } } } } -@Composable -private fun HandleStartGooglePay( - startGooglePayData: SessionsStartGooglePayData?, - onGooglePayStarted: () -> Unit -) { - if (startGooglePayData == null) return - val googlePayComponent = getGooglePayComponent(componentData = startGooglePayData.componentData) - LaunchedEffect(startGooglePayData) { - googlePayComponent.startGooglePayScreen() - onGooglePayStarted() - } -} - -@Composable -private fun HandleAction( - actionToHandle: SessionsGooglePayAction?, - onActionConsumed: () -> Unit -) { - if (actionToHandle == null) return - val activity = LocalContext.current as Activity - val googlePayComponent = getGooglePayComponent(componentData = actionToHandle.componentData) - LaunchedEffect(actionToHandle) { - googlePayComponent.handleAction(actionToHandle.action, activity) - onActionConsumed() - } -} - -@Composable -private fun HandleNewIntent( - intentToHandle: SessionsGooglePayIntent?, - onNewIntentHandled: () -> Unit -) { - if (intentToHandle == null) return - val googlePayComponent = getGooglePayComponent(componentData = intentToHandle.componentData) - LaunchedEffect(intentToHandle) { - googlePayComponent.handleIntent(intentToHandle.intent) - onNewIntentHandled() - } -} - @Composable private fun getGooglePayComponent(componentData: SessionsGooglePayComponentData): GooglePayComponent { return with(componentData) { diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt index 94cbb465f6..5c765a1427 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt @@ -9,11 +9,16 @@ package com.adyen.checkout.example.ui.googlepay.compose import androidx.compose.runtime.Immutable +import com.adyen.checkout.example.ui.compose.ResultState -@Immutable -internal data class SessionsGooglePayState( - val uiState: SessionsGooglePayUIState, - val startGooglePay: SessionsStartGooglePayData? = null, - val actionToHandle: SessionsGooglePayAction? = null, - val intentToHandle: SessionsGooglePayIntent? = null, -) +internal sealed class SessionsGooglePayState { + + @Immutable + data object Loading : SessionsGooglePayState() + + @Immutable + data object ShowButton : SessionsGooglePayState() + + @Immutable + data class FinalResult(val finalResult: ResultState) : SessionsGooglePayState() +} diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index a0f55c55a7..5d5df697b4 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -59,12 +59,11 @@ internal class SessionsGooglePayViewModel @Inject constructor( private val checkoutConfiguration = checkoutConfigurationProvider.checkoutConfig - private val _googlePayState = MutableStateFlow(SessionsGooglePayState(SessionsGooglePayUIState.Loading)) + private val _googlePayState = MutableStateFlow(SessionsGooglePayState.Loading) val googlePayState: StateFlow = _googlePayState.asStateFlow() - private var _componentData: SessionsGooglePayComponentData? = null - private val componentData: SessionsGooglePayComponentData - get() = requireNotNull(_componentData) { "component data should not be null" } + private val _stateEvents: MutableStateFlow = MutableStateFlow(SessionsGooglePayEvents.None) + val stateEvents: StateFlow = _stateEvents.asStateFlow() init { viewModelScope.launch { fetchSession() } @@ -85,13 +84,14 @@ internal class SessionsGooglePayViewModel @Inject constructor( return@withContext } - _componentData = SessionsGooglePayComponentData( + val componentData = SessionsGooglePayComponentData( checkoutSession, checkoutConfiguration, paymentMethod, this@SessionsGooglePayViewModel, ) + updateEvent { SessionsGooglePayEvents.ComponentData(componentData) } checkGooglePayAvailability(paymentMethod, checkoutConfiguration) } @@ -143,7 +143,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { viewModelScope.launch { if (isAvailable) { - updateState { it.copy(uiState = SessionsGooglePayUIState.ShowButton(componentData)) } + updateState { SessionsGooglePayState.ShowButton } } else { onError() } @@ -151,7 +151,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( } override fun onAction(action: Action) { - updateState { it.copy(actionToHandle = SessionsGooglePayAction(componentData, action)) } + updateEvent { SessionsGooglePayEvents.Action(action) } } override fun onError(componentError: ComponentError) { @@ -160,9 +160,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( } override fun onFinished(result: SessionPaymentResult) { - updateState { - it.copy(uiState = SessionsGooglePayUIState.FinalResult(getFinalResultState(result))) - } + updateState { SessionsGooglePayState.FinalResult(getFinalResultState(result)) } } private fun getFinalResultState(result: SessionPaymentResult): ResultState = when (result.resultCode) { @@ -174,40 +172,22 @@ internal class SessionsGooglePayViewModel @Inject constructor( } private fun onError() { - updateState { it.copy(uiState = SessionsGooglePayUIState.FinalResult(ResultState.FAILURE)) } + updateState { SessionsGooglePayState.FinalResult(ResultState.FAILURE) } } private fun updateState(block: (SessionsGooglePayState) -> SessionsGooglePayState) { _googlePayState.update(block) } - fun onButtonClicked() { - updateState { - it.copy( - uiState = SessionsGooglePayUIState.ShowComponent(componentData), - startGooglePay = SessionsStartGooglePayData(componentData, ACTIVITY_RESULT_CODE), - ) - } - } - - fun onGooglePayStarted() { - updateState { it.copy(startGooglePay = null) } - } - - fun onActionConsumed() { - updateState { it.copy(actionToHandle = null) } + private fun updateEvent(block: (SessionsGooglePayEvents) -> SessionsGooglePayEvents) { + _stateEvents.update(block) } fun onNewIntent(intent: Intent) { - updateState { it.copy(intentToHandle = SessionsGooglePayIntent(componentData, intent)) } - } - - fun onNewIntentHandled() { - updateState { it.copy(intentToHandle = null) } + updateEvent { SessionsGooglePayEvents.Intent(intent) } } companion object { private val TAG = getLogTag() - private const val ACTIVITY_RESULT_CODE = 1 } } From ea127d4e566606891e19382233247f43bba0ec7d Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 10 Jun 2024 15:48:27 +0200 Subject: [PATCH 22/95] Use new flow in drop-in COAND-855 --- .../dropin/internal/ui/DropInActivity.kt | 16 ------- .../ui/GooglePayComponentDialogFragment.kt | 42 +++++++++++-------- .../dropin/internal/ui/GooglePayViewModel.kt | 14 +++---- .../layout/fragment_google_pay_component.xml | 15 +++++-- .../internal/ui/GooglePayFragment.kt | 2 +- .../googlepay/internal/ui/GooglePayView.kt | 4 +- .../action/internal/ui/TwintActionView.kt | 4 +- 7 files changed, 47 insertions(+), 50 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt index 1f89902b9e..237c69bc22 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt @@ -183,22 +183,6 @@ internal class DropInActivity : return baseContext.createLocalizedContext(locale) } - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - checkGooglePayActivityResult(requestCode, resultCode, data) - } - - private fun checkGooglePayActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != GOOGLE_PAY_REQUEST_CODE) return - val fragment = getFragmentByTag(COMPONENT_FRAGMENT_TAG) as? GooglePayComponentDialogFragment - if (fragment == null) { - adyenLog(AdyenLogLevel.ERROR) { "GooglePayComponentDialogFragment is not loaded" } - return - } - fragment.handleActivityResult(resultCode, data) - } - override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) adyenLog(AdyenLogLevel.DEBUG) { "onNewIntent" } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt index e3f2365467..5a8d811be4 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt @@ -8,15 +8,13 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.adyen.checkout.components.core.ActionComponentData import com.adyen.checkout.components.core.ComponentCallback import com.adyen.checkout.components.core.ComponentError @@ -24,17 +22,21 @@ import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.internal.util.adyenLog -import com.adyen.checkout.dropin.R +import com.adyen.checkout.dropin.databinding.FragmentGooglePayComponentBinding import com.adyen.checkout.dropin.internal.provider.getComponentFor import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class GooglePayComponentDialogFragment : DropInBottomSheetDialogFragment(), ComponentCallback { + private var _binding: FragmentGooglePayComponentBinding? = null + private val binding: FragmentGooglePayComponentBinding get() = requireNotNull(_binding) + private val googlePayViewModel: GooglePayViewModel by viewModels() private lateinit var paymentMethod: PaymentMethod @@ -44,27 +46,30 @@ internal class GooglePayComponentDialogFragment : adyenLog(AdyenLogLevel.DEBUG) { "onCreate" } super.onCreate(savedInstanceState) arguments?.let { + @Suppress("DEPRECATION") paymentMethod = it.getParcelable(PAYMENT_METHOD) ?: throw IllegalArgumentException("Payment method is null") } - - googlePayViewModel.fragmentLoaded() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { adyenLog(AdyenLogLevel.DEBUG) { "onCreateView" } - return inflater.inflate(R.layout.fragment_google_pay_component, container, false) + _binding = FragmentGooglePayComponentBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onViewCreated" } - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - googlePayViewModel.eventsFlow.collect { handleEvent(it) } - } - } - loadComponent() + + binding.componentView.attach(component, viewLifecycleOwner) + + googlePayViewModel.onFragmentLoaded() + + googlePayViewModel.eventsFlow + .onEach(::handleEvent) + .flowWithLifecycle(viewLifecycleOwner.lifecycle) + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun loadComponent() { @@ -102,7 +107,7 @@ internal class GooglePayComponentDialogFragment : private fun handleEvent(event: GooglePayFragmentEvent) { when (event) { is GooglePayFragmentEvent.StartGooglePay -> { - component.startGooglePayScreen(requireActivity(), DropInActivity.GOOGLE_PAY_REQUEST_CODE) + component.startGooglePayScreen() } } } @@ -127,8 +132,9 @@ internal class GooglePayComponentDialogFragment : return true } - fun handleActivityResult(resultCode: Int, data: Intent?) { - component.handleActivityResult(resultCode, data) + override fun onDestroyView() { + _binding = null + super.onDestroyView() } companion object { diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt index 49e3654a28..27eb23376a 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt @@ -10,7 +10,6 @@ package com.adyen.checkout.dropin.internal.ui import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.adyen.checkout.components.core.internal.SavedStateHandleContainer import com.adyen.checkout.components.core.internal.SavedStateHandleProperty import com.adyen.checkout.components.core.internal.util.bufferedChannel @@ -18,7 +17,6 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch internal class GooglePayViewModel( override val savedStateHandle: SavedStateHandle @@ -29,13 +27,11 @@ internal class GooglePayViewModel( private var isGooglePayStarted: Boolean? by SavedStateHandleProperty(IS_GOOGLE_PAY_STARTED) - fun fragmentLoaded() { + fun onFragmentLoaded() { + adyenLog(AdyenLogLevel.DEBUG) { "onFragmentLoaded" } if (isGooglePayStarted == true) return isGooglePayStarted = true - viewModelScope.launch { - adyenLog(AdyenLogLevel.DEBUG) { "Sending start GooglePay event" } - eventChannel.send(GooglePayFragmentEvent.StartGooglePay) - } + eventChannel.trySend(GooglePayFragmentEvent.StartGooglePay) } companion object { @@ -43,6 +39,6 @@ internal class GooglePayViewModel( } } -internal sealed class GooglePayFragmentEvent { - object StartGooglePay : GooglePayFragmentEvent() +internal abstract class GooglePayFragmentEvent { + data object StartGooglePay : GooglePayFragmentEvent() } diff --git a/drop-in/src/main/res/layout/fragment_google_pay_component.xml b/drop-in/src/main/res/layout/fragment_google_pay_component.xml index 07f50189f3..32cee25328 100644 --- a/drop-in/src/main/res/layout/fragment_google_pay_component.xml +++ b/drop-in/src/main/res/layout/fragment_google_pay_component.xml @@ -1,5 +1,4 @@ - - - \ No newline at end of file + android:layout_height="wrap_content" + android:orientation="vertical"> + + + + diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index 538df961c9..a7259d0a8e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -33,8 +33,8 @@ internal class GooglePayFragment : Fragment() { } fun initialize(delegate: GooglePayDelegate) { - delegate.setPaymentDataLauncher(googlePayLauncher) this.delegate = delegate + delegate.setPaymentDataLauncher(googlePayLauncher) } override fun onDestroyView() { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt index b9972cabe9..25256e3a32 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -18,7 +18,9 @@ import com.adyen.checkout.googlepay.databinding.ViewGooglePayBinding import com.adyen.checkout.ui.core.internal.ui.ComponentView import kotlinx.coroutines.CoroutineScope -internal class GooglePayView internal constructor( +internal class GooglePayView +@JvmOverloads +internal constructor( layoutInflater: LayoutInflater, attrs: AttributeSet? = null, defStyleAttr: Int = 0 diff --git a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt index ae7b1e483d..405785de68 100644 --- a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt +++ b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt @@ -18,7 +18,9 @@ import com.adyen.checkout.twint.action.databinding.ViewTwintActionBinding import com.adyen.checkout.ui.core.internal.ui.ComponentView import kotlinx.coroutines.CoroutineScope -internal class TwintActionView internal constructor( +internal class TwintActionView +@JvmOverloads +internal constructor( layoutInflater: LayoutInflater, attrs: AttributeSet? = null, defStyleAttr: Int = 0 From aac927dc2fa13a953180c96fdb8bc564dd153c79 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 10 Jun 2024 15:55:29 +0200 Subject: [PATCH 23/95] Deprecate old methods COAND-855 --- .../java/com/adyen/checkout/googlepay/GooglePayComponent.kt | 3 +++ .../googlepay/internal/ui/DefaultGooglePayDelegate.kt | 4 ++-- .../adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index dc14b90952..f984bae3e9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -79,7 +79,9 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { + @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) } @@ -104,6 +106,7 @@ class GooglePayComponent internal constructor( * @param resultCode The result code from the [Activity.onActivityResult] * @param data The data intent from the [Activity.onActivityResult] */ + @Deprecated("When using startGooglePayScreen() this method is no longer needed.", ReplaceWith("")) override fun handleActivityResult(resultCode: Int, data: Intent?) { googlePayDelegate.handleActivityResult(resultCode, data) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index d6ad97b5ca..c1d88af4c7 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -158,12 +158,12 @@ internal class DefaultGooglePayDelegate( ) } + // TODO remove in v6 override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) - // TODO this forces us to use the deprecated onActivityResult. Look into alternatives when/if Google provides - // any later. + @Suppress("DEPRECATION") AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 0616e26da8..f8cefa3b66 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -29,6 +29,7 @@ internal interface GooglePayDelegate : val exceptionFlow: Flow + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) fun startGooglePayScreen() From d929a25f3990729b4e41d0d78e794d448a3d4d29 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 10:19:03 +0200 Subject: [PATCH 24/95] Use a flow to trigger the payment data launcher COAND-855 --- .../internal/ui/DefaultGooglePayDelegate.kt | 12 ++++-------- .../googlepay/internal/ui/GooglePayDelegate.kt | 4 ++-- .../googlepay/internal/ui/GooglePayFragment.kt | 7 ++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index c1d88af4c7..df290213fe 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -11,7 +11,6 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.app.Application import android.content.Intent -import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.components.core.OrderRequest @@ -75,7 +74,8 @@ internal class DefaultGooglePayDelegate( private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) - private lateinit var paymentDataLauncher: ActivityResultLauncher> + private val payEventChannel: Channel> = bufferedChannel() + override val payEventFlow: Flow> = payEventChannel.receiveAsFlow() override fun initialize(coroutineScope: CoroutineScope) { _coroutineScope = coroutineScope @@ -95,10 +95,6 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } - override fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) { - this.paymentDataLauncher = paymentDataLauncher - } - private fun onState(state: GooglePayComponentState) { if (state.isValid) { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) @@ -158,7 +154,7 @@ internal class DefaultGooglePayDelegate( ) } - // TODO remove in v6 + @Deprecated("Deprecated in favor of startGooglePayScreen()", replaceWith = ReplaceWith("startGooglePayScreen()")) override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) @@ -173,7 +169,7 @@ internal class DefaultGooglePayDelegate( val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { - paymentDataLauncher.launch(paymentDataTask.awaitTask()) + payEventChannel.send(paymentDataTask.awaitTask()) } } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index f8cefa3b66..52f72d5053 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -29,6 +29,8 @@ internal interface GooglePayDelegate : val exceptionFlow: Flow + val payEventFlow: Flow> + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) @@ -39,6 +41,4 @@ internal interface GooglePayDelegate : fun getGooglePayButtonParameters(): GooglePayButtonParameters fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) - - fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index a7259d0a8e..1f54d1b571 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -13,8 +13,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding import com.google.android.gms.wallet.contract.TaskResultContracts +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal class GooglePayFragment : Fragment() { @@ -34,7 +37,9 @@ internal class GooglePayFragment : Fragment() { fun initialize(delegate: GooglePayDelegate) { this.delegate = delegate - delegate.setPaymentDataLauncher(googlePayLauncher) + delegate.payEventFlow + .onEach { googlePayLauncher.launch(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun onDestroyView() { From 2537769a42d321d89d57de25aea8c151bc21cc46 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 10:52:13 +0200 Subject: [PATCH 25/95] Use submit() to start Google Pay flow COAND-855 --- .../ui/GooglePayComponentDialogFragment.kt | 2 +- .../example/ui/googlepay/GooglePayFragment.kt | 3 +- .../compose/SessionsGooglePayScreen.kt | 2 +- .../checkout/googlepay/GooglePayComponent.kt | 14 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 12 ++- .../internal/ui/GooglePayDelegate.kt | 9 +- .../googlepay/GooglePayComponentTest.kt | 96 ++++++++++++++++--- .../ui/DefaultGooglePayDelegateTest.kt | 2 + 8 files changed, 112 insertions(+), 28 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt index 5a8d811be4..8d0abe3465 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt @@ -107,7 +107,7 @@ internal class GooglePayComponentDialogFragment : private fun handleEvent(event: GooglePayFragmentEvent) { when (event) { is GooglePayFragmentEvent.StartGooglePay -> { - component.startGooglePayScreen() + component.submit() } } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index 4f8feba409..b5a84815b6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -147,7 +147,7 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.googlePayButton.initialize(buttonOptions) binding.googlePayButton.setOnClickListener { - googlePayComponent?.startGooglePayScreen() + googlePayComponent?.submit() } } @@ -162,7 +162,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { internal val TAG = getLogTag() internal const val RETURN_URL_EXTRA = "RETURN_URL_EXTRA" - internal const val ACTIVITY_RESULT_CODE = 1 fun show(fragmentManager: FragmentManager) { GooglePayFragment().show(fragmentManager, TAG) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 9f1b59d8ae..51dc35c4db 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -110,7 +110,7 @@ private fun SessionsGooglePayContent( AdyenComponent(googlePayComponent) PayButton( - onClick = { googlePayComponent.startGooglePayScreen() }, + onClick = { googlePayComponent.submit() }, allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, type = ButtonType.Pay, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index f984bae3e9..810dd88e0e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.internal.ActivityResultHandlingComponent +import com.adyen.checkout.components.core.internal.ButtonComponent import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponent import com.adyen.checkout.components.core.internal.PaymentComponentEvent @@ -26,6 +27,7 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewableComponent import com.adyen.checkout.ui.core.internal.util.mergeViewFlows @@ -43,6 +45,7 @@ class GooglePayComponent internal constructor( ) : ViewModel(), PaymentComponent, ActivityResultHandlingComponent, + ButtonComponent, ViewableComponent, ActionHandlingComponent by actionHandlingComponent { @@ -79,7 +82,7 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("submit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) @@ -88,10 +91,13 @@ class GooglePayComponent internal constructor( /** * Start the GooglePay screen. */ - fun startGooglePayScreen() { - googlePayDelegate.startGooglePayScreen() + override fun submit() { + (delegate as? ButtonDelegate)?.onSubmit() + ?: adyenLog(AdyenLogLevel.ERROR) { "Component is currently not submittable, ignoring." } } + override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false + /** * Returns some of the parameters required to initialize the [Google Pay button](https://docs.adyen.com/payment-methods/google-pay/android-component/#2-show-the-google-pay-button). */ @@ -106,7 +112,7 @@ class GooglePayComponent internal constructor( * @param resultCode The result code from the [Activity.onActivityResult] * @param data The data intent from the [Activity.onActivityResult] */ - @Deprecated("When using startGooglePayScreen() this method is no longer needed.", ReplaceWith("")) + @Deprecated("When using submit() this method is no longer needed.", ReplaceWith("")) override fun handleActivityResult(resultCode: Int, data: Intent?) { googlePayDelegate.handleActivityResult(resultCode, data) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index df290213fe..aecab40096 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -69,7 +69,8 @@ internal class DefaultGooglePayDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() - override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + override val viewFlow: Flow = _viewFlow private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) @@ -163,7 +164,7 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } - override fun startGooglePayScreen() { + override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) @@ -255,6 +256,13 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } + // Currently, we don't show any button, but we could potentially show the Google Pay button. + override fun isConfirmationRequired(): Boolean = false + + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() + + override fun shouldEnableSubmitButton(): Boolean = shouldShowSubmitButton() + override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 52f72d5053..77ee7a403e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -10,11 +10,11 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.content.Intent -import androidx.activity.result.ActivityResultLauncher import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.PaymentData @@ -23,7 +23,8 @@ import kotlinx.coroutines.flow.Flow internal interface GooglePayDelegate : PaymentComponentDelegate, - ViewProvidingDelegate { + ViewProvidingDelegate, + ButtonDelegate { val componentStateFlow: Flow @@ -31,11 +32,9 @@ internal interface GooglePayDelegate : val payEventFlow: Flow> - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("onSubmit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) - fun startGooglePayScreen() - fun handleActivityResult(resultCode: Int, data: Intent?) fun getGooglePayButtonParameters(): GooglePayButtonParameters diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt index c4cf36944b..6f59f59e13 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt @@ -12,20 +12,20 @@ import android.app.Activity import android.content.Intent import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope -import app.cash.turbine.test import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.googlepay.internal.ui.GooglePayComponentViewType import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared +import com.adyen.checkout.test.extensions.test import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -35,6 +35,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -50,6 +51,7 @@ internal class GooglePayComponentTest( @BeforeEach fun before() { + whenever(googlePayDelegate.viewFlow) doReturn MutableStateFlow(GooglePayComponentViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = GooglePayComponent( @@ -96,13 +98,33 @@ internal class GooglePayComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { - component.viewFlow.test { - assertNull(awaitItem()) - expectNoEvents() - } + fun `when component is initialized, then view flow should match google pay delegate view flow`() = runTest { + val viewFlow = component.viewFlow.test(testScheduler) + + assertEquals(GooglePayComponentViewType, viewFlow.latestValue) } + @Test + fun `when cash app pay delegate view flow emits a value then component view flow should match that value`() = + runTest { + val delegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(googlePayDelegate.viewFlow) doReturn delegateViewFlow + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + + val testViewFlow = component.viewFlow.test(testScheduler) + + assertEquals(TestComponentViewType.VIEW_TYPE_1, testViewFlow.latestValue) + + delegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + + assertEquals(TestComponentViewType.VIEW_TYPE_2, testViewFlow.latestValue) + } + @Test fun `when action delegate view flow emits a value then component view flow should match that value`() = runTest { val actionDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) @@ -114,16 +136,18 @@ internal class GooglePayComponentTest( componentEventHandler = componentEventHandler, ) - component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + val testViewFlow = component.viewFlow.test(testScheduler) - actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) - assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(GooglePayComponentViewType, testViewFlow.latestValue) - expectNoEvents() - } + actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + + assertEquals(TestComponentViewType.VIEW_TYPE_2, testViewFlow.latestValue) } + @Suppress("DEPRECATION") @Test fun `when startGooglePayScreen is called then delegate startGooglePayScreen is called`() { val activity = Activity() @@ -132,6 +156,7 @@ internal class GooglePayComponentTest( verify(googlePayDelegate).startGooglePayScreen(activity, requestCode) } + @Suppress("DEPRECATION") @Test fun `when handleActivityResult is called then delegate handleActivityResult is called`() { val requestCode = 1 @@ -139,4 +164,49 @@ internal class GooglePayComponentTest( component.handleActivityResult(requestCode, intent) verify(googlePayDelegate).handleActivityResult(requestCode, intent) } + + @Test + fun `when submit is called and active delegate is the payment delegate, then delegate onSubmit is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.submit() + + verify(googlePayDelegate).onSubmit() + } + + @Test + fun `when submit is called and active delegate is the action delegate, then delegate onSubmit is not called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(genericActionDelegate) + + component.submit() + + verify(googlePayDelegate, never()).onSubmit() + } + + @Test + fun `when isConfirmationRequired and delegate is default, then delegate is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.isConfirmationRequired() + + verify(googlePayDelegate).isConfirmationRequired() + } } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 5c26c122da..f586235281 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -9,6 +9,7 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity +import android.app.Application import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration @@ -225,6 +226,7 @@ internal class DefaultGooglePayDelegateTest { componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()) .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, + application = Application(), ) } From c173ffd8ef1cc445c8a11b8884368336f008b219 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 15:38:26 +0200 Subject: [PATCH 26/95] Move PaymentsClient to constructor This makes the code interacting with it testable. COAND-855 --- .../provider/GooglePayComponentProvider.kt | 10 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 5 +- .../googlepay/internal/util/TaskExtensions.kt | 4 +- .../ui/DefaultGooglePayDelegateTest.kt | 75 ++++++++++++++++--- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index e6e081c479..b845ba3971 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -108,13 +108,16 @@ constructor( sessionId = null, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = order, componentParams = componentParams, analyticsManager = analyticsManager, - application = application, + paymentsClient = paymentsClient, ) val genericActionDelegate = @@ -196,13 +199,16 @@ constructor( sessionId = checkoutSession.sessionSetupResponse.id, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = checkoutSession.order, componentParams = componentParams, analyticsManager = analyticsManager, - application = application, + paymentsClient = paymentsClient, ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index aecab40096..3bb1b0f8a7 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity -import android.app.Application import android.content.Intent import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner @@ -39,6 +38,7 @@ import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentsClient import com.google.android.gms.wallet.Wallet import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.CoroutineScope @@ -57,7 +57,7 @@ internal class DefaultGooglePayDelegate( private val order: OrderRequest?, override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, - private val application: Application, + private val paymentsClient: PaymentsClient, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -166,7 +166,6 @@ internal class DefaultGooglePayDelegate( override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } - val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt index d2a843f58d..8087675b87 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt @@ -20,7 +20,7 @@ internal suspend fun Task.awaitTask(cancellationTokenSource: Cancellation } else { suspendCancellableCoroutine { cont -> // Run the callback directly to avoid unnecessarily scheduling on the main thread. - addOnCompleteListener(DirectExecutor, cont::resume) + addOnCompleteListener(DirectExecutor(), cont::resume) cancellationTokenSource?.let { cancellationSource -> cont.invokeOnCancellation { cancellationSource.cancel() } @@ -32,7 +32,7 @@ internal suspend fun Task.awaitTask(cancellationTokenSource: Cancellation /** * An [Executor] that just directly executes the [Runnable]. */ -private object DirectExecutor : Executor { +private class DirectExecutor : Executor { override fun execute(runnable: Runnable) { runnable.run() } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index f586235281..d900a59575 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity -import android.app.Application import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration @@ -22,17 +21,26 @@ import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment +import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.test.LoggingExtension +import com.adyen.checkout.test.extensions.test +import com.google.android.gms.common.api.Status +import com.google.android.gms.tasks.Tasks +import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentsClient +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -42,19 +50,22 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource +import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(MockitoExtension::class) -internal class DefaultGooglePayDelegateTest { +@ExtendWith(MockitoExtension::class, LoggingExtension::class) +internal class DefaultGooglePayDelegateTest( + @Mock private val paymentsClient: PaymentsClient, +) { private lateinit var analyticsManager: TestAnalyticsManager private lateinit var delegate: DefaultGooglePayDelegate - private val paymentData: PaymentData - get() = PaymentData.fromJson("{\"paymentMethodData\": {\"tokenizationData\": {\"token\": \"test_token\"}}}") - @BeforeEach fun beforeEach() { analyticsManager = TestAnalyticsManager() @@ -96,7 +107,7 @@ internal class DefaultGooglePayDelegateTest { delegate.componentStateFlow.test { skipItems(1) - val paymentData = paymentData + val paymentData = TEST_PAYMENT_DATA delegate.updateComponentState(paymentData) @@ -124,6 +135,18 @@ internal class DefaultGooglePayDelegateTest { } } + @Test + fun `when onSubmit is called, then event is emitted to start Google Pay`() = runTest { + val task = Tasks.forResult(TEST_PAYMENT_DATA) + whenever(paymentsClient.loadPaymentData(any())) doReturn task + val payEventFlow = delegate.payEventFlow.test(testScheduler) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.onSubmit() + + assertEquals(task, payEventFlow.latestValue) + } + @ParameterizedTest @MethodSource("amountSource") fun `when input data is valid then amount is propagated in component state if set`( @@ -136,7 +159,7 @@ internal class DefaultGooglePayDelegateTest { } delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) } } @@ -163,7 +186,7 @@ internal class DefaultGooglePayDelegateTest { fun `when component state updates amd the data is valid, then submit event is tracked`() { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) analyticsManager.assertLastEventEquals(expectedEvent) @@ -176,7 +199,7 @@ internal class DefaultGooglePayDelegateTest { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) } @@ -201,6 +224,24 @@ internal class DefaultGooglePayDelegateTest { } } + @ParameterizedTest + @MethodSource("paymentResultSource") + fun `when handling payment result, then success or error is emitted`( + result: ApiTaskResult, + isSuccess: Boolean, + ) = runTest { + val componentStateFlow = delegate.componentStateFlow.test(testScheduler) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + delegate.handlePaymentResult(result) + + if (isSuccess) { + assertEquals(result.result, componentStateFlow.latestValue.paymentData) + } else { + assertInstanceOf(ComponentException::class.java, exceptionFlow.latestValue) + } + } + private fun createCheckoutConfiguration( amount: Amount? = null, configuration: GooglePayConfiguration.Builder.() -> Unit = {} @@ -226,7 +267,7 @@ internal class DefaultGooglePayDelegateTest { componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()) .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, - application = Application(), + paymentsClient = paymentsClient, ) } @@ -234,6 +275,8 @@ internal class DefaultGooglePayDelegateTest { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" private const val TEST_PAYMENT_METHOD_TYPE = "TEST_PAYMENT_METHOD_TYPE" + private val TEST_PAYMENT_DATA: PaymentData = + PaymentData.fromJson("{\"paymentMethodData\": {\"tokenizationData\": {\"token\": \"test_token\"}}}") @JvmStatic fun amountSource() = listOf( @@ -243,5 +286,15 @@ internal class DefaultGooglePayDelegateTest { arguments(null, Amount("USD", 0)), arguments(null, Amount("USD", 0)), ) + + @JvmStatic + fun paymentResultSource() = listOf( + arguments(ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS), true), + arguments(ApiTaskResult(null, Status.RESULT_SUCCESS), false), + arguments(ApiTaskResult(null, Status.RESULT_CANCELED), false), + arguments(ApiTaskResult(null, Status.RESULT_INTERNAL_ERROR), false), + arguments(ApiTaskResult(null, Status.RESULT_INTERRUPTED), false), + arguments(ApiTaskResult(null, Status(AutoResolveHelper.RESULT_ERROR)), false), + ) } } From a668ca2adf5fc6546712c06ad5ccc33012bb0f1b Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 15:47:21 +0200 Subject: [PATCH 27/95] Add release note COAND-855 --- RELEASE_NOTES.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bf2eb83b92..f6d200830b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,23 +9,16 @@ [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) ## New -- Payment method: - - Pay by Bank US. Payment method type: **paybybank_AIS_DD**. +- Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. ## Fixed -- For cards, the address lookup functionality no longer crashes if the shopper presses back when the postal code field is in focus. -- For Drop-in, fixed an issue where the error dialog showed loading state in some edge cases. + +## Improved ## Changed - Dependency versions: | Name | Version | |--------------------------------------------------------------------------------------------------------|-------------------------------| - | [Android Gradle Plugin](https://developer.android.com/build/releases/gradle-plugin#android-gradle-plugin-8.7.1) | **8.7.1** | - | [AndroidX Activity](https://developer.android.com/jetpack/androidx/releases/activity#1.9.3) | **1.9.3** | - | [AndroidX Annotation](https://developer.android.com/jetpack/androidx/releases/annotation#1.9.1) | **1.9.1** | - | [AndroidX Autofill](https://developer.android.com/jetpack/androidx/releases/autofill#1.3.0-beta01) | **1.3.0-beta01** | - | [AndroidX Compose Activity](https://developer.android.com/jetpack/androidx/releases/activity#1.9.3) | **1.9.3** | - | [AndroidX Compose BOM](https://developer.android.com/develop/ui/compose/bom/bom-mapping) | **2024.10.00** | - | [AndroidX Fragment](https://developer.android.com/jetpack/androidx/releases/fragment#1.8.5) | **1.8.5** | - | [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.6) | **2.8.6** | - | [AndroidX Lifecycle ViewModel Compose](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.6) | **2.8.6** | + | | | + +## Deprecated From 41dd522b5e2d4c21240c8cdf884b74e960e63741 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 24 Jun 2024 15:59:36 +0200 Subject: [PATCH 28/95] Run apiDump COAND-855 --- googlepay/api/googlepay.api | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index dc8b4cabca..6175187b12 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -65,7 +65,7 @@ public final class com/adyen/checkout/googlepay/GooglePayButtonParameters { public fun toString ()Ljava/lang/String; } -public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { +public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/ButtonComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { public static final field Companion Lcom/adyen/checkout/googlepay/GooglePayComponent$Companion; public static final field PAYMENT_METHOD_TYPES Ljava/util/List; public static final field PROVIDER Lcom/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider; @@ -76,9 +76,11 @@ public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/li public fun handleAction (Lcom/adyen/checkout/components/core/action/Action;Landroid/app/Activity;)V public fun handleActivityResult (ILandroid/content/Intent;)V public fun handleIntent (Landroid/content/Intent;)V + public fun isConfirmationRequired ()Z public fun setInteractionBlocked (Z)V public fun setOnRedirectListener (Lkotlin/jvm/functions/Function0;)V public final fun startGooglePayScreen (Landroid/app/Activity;I)V + public fun submit ()V } public final class com/adyen/checkout/googlepay/GooglePayComponent$Companion { From 02bd29ba48b585b31f997b8c6b54ff2027b27d0b Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 20 Sep 2024 16:23:13 +0200 Subject: [PATCH 29/95] Fix build after rebase COAND-855 --- .../checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 3bb1b0f8a7..dce829abcc 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -260,8 +260,6 @@ internal class DefaultGooglePayDelegate( override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() - override fun shouldEnableSubmitButton(): Boolean = shouldShowSubmitButton() - override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } From 4a6184956f657885aaeadc04ab0713d1d147c785 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 26 Jun 2024 13:45:19 +0200 Subject: [PATCH 30/95] Add configuration for submit button visibility in Google Pay COAND-942 --- .../googlepay/GooglePayConfiguration.kt | 26 ++++++++++++++++--- .../ui/model/GooglePayComponentParams.kt | 4 ++- .../model/GooglePayComponentParamsMapper.kt | 1 + .../googlepay/GooglePayConfigurationTest.kt | 4 +++ .../GooglePayComponentParamsMapperTest.kt | 11 +++++--- .../internal/util/GooglePayUtilsTest.kt | 2 ++ 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt index 70a2bb0b4f..5593701902 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt @@ -16,6 +16,8 @@ import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.AnalyticsConfiguration import com.adyen.checkout.components.core.CheckoutConfiguration import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.ButtonConfiguration +import com.adyen.checkout.components.core.internal.ButtonConfigurationBuilder import com.adyen.checkout.components.core.internal.Configuration import com.adyen.checkout.components.core.internal.util.CheckoutConfigurationMarker import com.adyen.checkout.core.Environment @@ -34,9 +36,10 @@ class GooglePayConfiguration private constructor( override val environment: Environment, override val clientKey: String, override val analyticsConfiguration: AnalyticsConfiguration?, + override val amount: Amount?, + override val isSubmitButtonVisible: Boolean?, val merchantAccount: String?, val googlePayEnvironment: Int?, - override val amount: Amount?, val totalPriceStatus: String?, val countryCode: String?, val merchantInfo: MerchantInfo?, @@ -52,14 +55,15 @@ class GooglePayConfiguration private constructor( val isBillingAddressRequired: Boolean?, val billingAddressParameters: BillingAddressParameters?, internal val genericActionConfiguration: GenericActionConfiguration, -) : Configuration { +) : Configuration, ButtonConfiguration { /** * Builder to create a [GooglePayConfiguration]. */ @Suppress("TooManyFunctions") class Builder : - ActionHandlingPaymentMethodConfigurationBuilder { + ActionHandlingPaymentMethodConfigurationBuilder, + ButtonConfigurationBuilder { private var merchantAccount: String? = null private var googlePayEnvironment: Int? = null private var merchantInfo: MerchantInfo? = null @@ -76,6 +80,7 @@ class GooglePayConfiguration private constructor( private var isBillingAddressRequired: Boolean? = null private var billingAddressParameters: BillingAddressParameters? = null private var totalPriceStatus: String? = null + private var isSubmitButtonVisible: Boolean? = null /** * Initialize a configuration builder with the required fields. @@ -378,15 +383,28 @@ class GooglePayConfiguration private constructor( return super.setAmount(amount) } + /** + * Sets if submit button will be visible or not. + * + * Default is false. + * + * @param isSubmitButtonVisible If submit button should be visible or not. + */ + override fun setSubmitButtonVisible(isSubmitButtonVisible: Boolean): Builder { + this.isSubmitButtonVisible = isSubmitButtonVisible + return this + } + override fun buildInternal(): GooglePayConfiguration { return GooglePayConfiguration( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, analyticsConfiguration = analyticsConfiguration, + amount = amount, + isSubmitButtonVisible = isSubmitButtonVisible, merchantAccount = merchantAccount, googlePayEnvironment = googlePayEnvironment, - amount = amount, totalPriceStatus = totalPriceStatus, countryCode = countryCode, merchantInfo = merchantInfo, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt index e6c7526487..b09994cc08 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt @@ -9,6 +9,7 @@ package com.adyen.checkout.googlepay.internal.ui.model import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.internal.ui.model.ButtonParams import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.googlepay.BillingAddressParameters @@ -18,6 +19,7 @@ import com.adyen.checkout.googlepay.ShippingAddressParameters internal data class GooglePayComponentParams( private val commonComponentParams: CommonComponentParams, override val amount: Amount, + override val isSubmitButtonVisible: Boolean, val gatewayMerchantId: String, val googlePayEnvironment: Int, val totalPriceStatus: String, @@ -34,4 +36,4 @@ internal data class GooglePayComponentParams( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean, val billingAddressParameters: BillingAddressParameters?, -) : ComponentParams by commonComponentParams +) : ComponentParams by commonComponentParams, ButtonParams diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index ee737a5192..ded32f9d7c 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -60,6 +60,7 @@ internal class GooglePayComponentParamsMapper( return GooglePayComponentParams( commonComponentParams = commonComponentParams, amount = commonComponentParams.amount ?: DEFAULT_AMOUNT, + isSubmitButtonVisible = googlePayConfiguration?.isSubmitButtonVisible ?: false, gatewayMerchantId = googlePayConfiguration.getPreferredGatewayMerchantId(paymentMethod), allowedAuthMethods = googlePayConfiguration.getAvailableAuthMethods(), allowedCardNetworks = googlePayConfiguration.getAvailableCardNetworks(paymentMethod), diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt index e75b35bf10..3160310d55 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt @@ -22,6 +22,7 @@ internal class GooglePayConfigurationTest { analyticsConfiguration = AnalyticsConfiguration(AnalyticsLevel.ALL), ) { googlePay { + setSubmitButtonVisible(true) setMerchantAccount("merchantAccount") setGooglePayEnvironment(WalletConstants.ENVIRONMENT_PRODUCTION) setMerchantInfo(MerchantInfo(merchantId = "id")) @@ -48,6 +49,7 @@ internal class GooglePayConfigurationTest { environment = Environment.TEST, clientKey = TEST_CLIENT_KEY, ) + .setSubmitButtonVisible(true) .setAmount(Amount("EUR", 123L)) .setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) .setMerchantAccount("merchantAccount") @@ -99,6 +101,7 @@ internal class GooglePayConfigurationTest { clientKey = TEST_CLIENT_KEY, ) .setAmount(Amount("EUR", 123L)) + .setSubmitButtonVisible(true) .setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) .setMerchantAccount("merchantAccount") .setGooglePayEnvironment(WalletConstants.ENVIRONMENT_PRODUCTION) @@ -139,6 +142,7 @@ internal class GooglePayConfigurationTest { assertEquals(config.environment, actualGooglePayCardConfig?.environment) assertEquals(config.clientKey, actualGooglePayCardConfig?.clientKey) assertEquals(config.amount, actualGooglePayCardConfig?.amount) + assertEquals(config.isSubmitButtonVisible, actualGooglePayCardConfig?.isSubmitButtonVisible) assertEquals(config.analyticsConfiguration, actualGooglePayCardConfig?.analyticsConfiguration) assertEquals(config.merchantAccount, actualGooglePayCardConfig?.merchantAccount) assertEquals(config.googlePayEnvironment, actualGooglePayCardConfig?.googlePayEnvironment) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index 6eda610890..0054fef7e5 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -111,9 +111,10 @@ internal class GooglePayComponentParamsMapperTest { environment = Environment.APSE, clientKey = TEST_CLIENT_KEY_2, analyticsParams = AnalyticsParams(AnalyticsParamsLevel.ALL, TEST_CLIENT_KEY_2), + amount = amount, + isSubmitButtonVisible = false, gatewayMerchantId = "MERCHANT_ACCOUNT", googlePayEnvironment = WalletConstants.ENVIRONMENT_PRODUCTION, - amount = amount, totalPriceStatus = "STATUS", countryCode = "ZZ", merchantInfo = merchantInfo, @@ -148,6 +149,7 @@ internal class GooglePayComponentParamsMapperTest { googlePay { setAmount(Amount("USD", 1L)) setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) + setSubmitButtonVisible(true) setMerchantAccount(TEST_GATEWAY_MERCHANT_ID) } } @@ -172,6 +174,7 @@ internal class GooglePayComponentParamsMapperTest { currency = "CAD", value = 123L, ), + isSubmitButtonVisible = true, ) assertEquals(expected, params) @@ -524,9 +527,10 @@ internal class GooglePayComponentParamsMapperTest { clientKey: String = TEST_CLIENT_KEY_1, analyticsParams: AnalyticsParams = AnalyticsParams(AnalyticsParamsLevel.ALL, TEST_CLIENT_KEY_1), isCreatedByDropIn: Boolean = false, + amount: Amount? = null, + isSubmitButtonVisible: Boolean = false, gatewayMerchantId: String = TEST_GATEWAY_MERCHANT_ID, googlePayEnvironment: Int = WalletConstants.ENVIRONMENT_TEST, - amount: Amount? = null, totalPriceStatus: String = "FINAL", countryCode: String? = null, merchantInfo: MerchantInfo? = null, @@ -550,9 +554,10 @@ internal class GooglePayComponentParamsMapperTest { isCreatedByDropIn = isCreatedByDropIn, amount = amount, ), + amount = amount ?: Amount("USD", 0), + isSubmitButtonVisible = isSubmitButtonVisible, gatewayMerchantId = gatewayMerchantId, googlePayEnvironment = googlePayEnvironment, - amount = amount ?: Amount("USD", 0), totalPriceStatus = totalPriceStatus, countryCode = countryCode, merchantInfo = merchantInfo, diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt index 8d33d0ccf6..670bb52e00 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt @@ -248,6 +248,7 @@ internal class GooglePayUtilsTest { amount = null, ), amount = Amount("USD", 0), + isSubmitButtonVisible = false, gatewayMerchantId = "", googlePayEnvironment = WalletConstants.ENVIRONMENT_TEST, totalPriceStatus = "NOT_CURRENTLY_KNOWN", @@ -278,6 +279,7 @@ internal class GooglePayUtilsTest { amount = Amount("EUR", 13_37), ), amount = Amount("EUR", 13_37), + isSubmitButtonVisible = true, gatewayMerchantId = "GATEWAY_MERCHANT_ID", googlePayEnvironment = WalletConstants.ENVIRONMENT_PRODUCTION, totalPriceStatus = "TOTAL_PRICE_STATUS", From 630cc007b8db0ddbed63751b8dfc577ef1d22798 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 26 Jun 2024 15:01:13 +0200 Subject: [PATCH 31/95] Show pay button within component COAND-942 --- .../internal/ui/view/CashAppPayButtonView.kt | 3 + .../CheckoutConfigurationProvider.kt | 1 + .../example/ui/googlepay/GooglePayFragment.kt | 28 --------- .../ui/googlepay/GooglePayViewModel.kt | 2 +- .../ui/googlepay/GooglePayViewState.kt | 2 - .../compose/SessionsGooglePayActivity.kt | 1 - .../compose/SessionsGooglePayScreen.kt | 13 ---- .../main/res/layout/fragment_google_pay.xml | 13 ---- googlepay/api/googlepay.api | 9 ++- .../checkout/googlepay/GooglePayComponent.kt | 8 +-- .../provider/GooglePayComponentProvider.kt | 3 + .../internal/ui/DefaultGooglePayDelegate.kt | 60 +++++++++---------- .../internal/ui/GooglePayButtonView.kt | 48 +++++++++++++++ .../internal/ui/GooglePayViewProvider.kt | 13 +++- .../model/GooglePayComponentParamsMapper.kt | 13 +++- .../res/layout/view_google_pay_button.xml | 19 ++++++ .../ui/DefaultGooglePayDelegateTest.kt | 27 +++++---- .../GooglePayComponentParamsMapperTest.kt | 2 +- .../checkout/ui/core/AdyenComponentView.kt | 1 + .../core/internal/ui/view/DefaultPayButton.kt | 3 + .../ui/core/internal/ui/view/PayButton.kt | 3 + 21 files changed, 161 insertions(+), 111 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt create mode 100644 googlepay/src/main/res/layout/view_google_pay_button.xml diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt index 61c0bedb14..723facce10 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.cashapppay.databinding.CashAppPayButtonViewBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton internal class CashAppPayButtonView @JvmOverloads constructor( @@ -22,6 +23,8 @@ internal class CashAppPayButtonView @JvmOverloads constructor( private val binding = CashAppPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + override fun initialize(delegate: ButtonDelegate) = Unit + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt index 4d35bcc87b..811d2b9489 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt @@ -89,6 +89,7 @@ internal class CheckoutConfigurationProvider @Inject constructor( } googlePay { + setSubmitButtonVisible(true) setCountryCode(keyValueStorage.getCountry()) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index b5a84815b6..0af398bdf5 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -24,8 +24,6 @@ import com.adyen.checkout.example.extensions.getLogTag import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.redirect.RedirectComponent -import com.google.android.gms.wallet.button.ButtonConstants.ButtonType -import com.google.android.gms.wallet.button.ButtonOptions import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn @@ -88,8 +86,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { this.googlePayComponent = googlePayComponent binding.componentView.attach(googlePayComponent, viewLifecycleOwner) - - loadGooglePayButton() } private fun onViewState(state: GooglePayViewState) { @@ -99,28 +95,18 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.errorView.text = getString(state.message) binding.componentView.isVisible = false binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = false } GooglePayViewState.Loading -> { binding.errorView.isVisible = false binding.componentView.isVisible = false binding.progressIndicator.isVisible = true - binding.googlePayButton.isVisible = false - } - - GooglePayViewState.ShowButton -> { - binding.errorView.isVisible = false - binding.componentView.isVisible = false - binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = true } GooglePayViewState.ShowComponent -> { binding.errorView.isVisible = false binding.componentView.isVisible = true binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = false } } } @@ -137,20 +123,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { dismiss() } - private fun loadGooglePayButton() { - val allowedPaymentMethods = googlePayComponent?.getGooglePayButtonParameters()?.allowedPaymentMethods.orEmpty() - val buttonOptions = ButtonOptions - .newBuilder() - .setButtonType(ButtonType.PAY) - .setAllowedPaymentMethods(allowedPaymentMethods) - .build() - binding.googlePayButton.initialize(buttonOptions) - - binding.googlePayButton.setOnClickListener { - googlePayComponent?.submit() - } - } - override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index c4d3be339c..d19ba0dd1c 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -114,7 +114,7 @@ internal class GooglePayViewModel @Inject constructor( override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { viewModelScope.launch { if (isAvailable) { - _viewState.emit(GooglePayViewState.ShowButton) + _viewState.emit(GooglePayViewState.ShowComponent) } else { _viewState.emit(GooglePayViewState.Error(R.string.google_pay_unavailable_error)) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt index d9bceb0940..d66232b067 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt @@ -14,8 +14,6 @@ internal sealed class GooglePayViewState { data object Loading : GooglePayViewState() - data object ShowButton : GooglePayViewState() - data object ShowComponent : GooglePayViewState() data class Error(@StringRes val message: Int) : GooglePayViewState() diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 83f46fcd62..0d66f76d3b 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -46,7 +46,6 @@ class SessionsGooglePayActivity : AppCompatActivity() { val isDarkTheme = uiThemeRepository.isDarkTheme() ExampleTheme(isDarkTheme) { SessionsGooglePayScreen( - useDarkTheme = isDarkTheme, onBackPressed = { onBackPressedDispatcher.onBackPressed() }, googlePayState = googlePayState, eventsState = eventsState, diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 51dc35c4db..701fe4ec4f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -35,13 +35,9 @@ import com.adyen.checkout.components.compose.AdyenComponent import com.adyen.checkout.components.compose.get import com.adyen.checkout.example.ui.compose.ResultContent import com.adyen.checkout.googlepay.GooglePayComponent -import com.google.pay.button.ButtonTheme -import com.google.pay.button.ButtonType -import com.google.pay.button.PayButton @Composable internal fun SessionsGooglePayScreen( - useDarkTheme: Boolean, googlePayState: SessionsGooglePayState, eventsState: SessionsGooglePayEvents, onBackPressed: () -> Unit, @@ -62,7 +58,6 @@ internal fun SessionsGooglePayScreen( SessionsGooglePayContent( googlePayState = googlePayState, googlePayEvents = eventsState, - useDarkTheme = useDarkTheme, modifier = Modifier.padding(innerPadding), ) } @@ -73,7 +68,6 @@ internal fun SessionsGooglePayScreen( private fun SessionsGooglePayContent( googlePayState: SessionsGooglePayState, googlePayEvents: SessionsGooglePayEvents, - useDarkTheme: Boolean, modifier: Modifier = Modifier, ) { val activity = LocalContext.current as Activity @@ -108,13 +102,6 @@ private fun SessionsGooglePayContent( is SessionsGooglePayState.ShowButton -> { AdyenComponent(googlePayComponent) - - PayButton( - onClick = { googlePayComponent.submit() }, - allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, - theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, - type = ButtonType.Pay, - ) } is SessionsGooglePayState.FinalResult -> { diff --git a/example-app/src/main/res/layout/fragment_google_pay.xml b/example-app/src/main/res/layout/fragment_google_pay.xml index fa75802986..2dce7d3b29 100644 --- a/example-app/src/main/res/layout/fragment_google_pay.xml +++ b/example-app/src/main/res/layout/fragment_google_pay.xml @@ -8,7 +8,6 @@ @@ -16,18 +15,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Ljava/lang/String;Ljava/lang/Integer;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun describeContents ()I public final fun getAllowedAuthMethods ()Ljava/util/List; public final fun getAllowedCardNetworks ()Ljava/util/List; @@ -129,10 +129,11 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/ady public final fun isEmailRequired ()Ljava/lang/Boolean; public final fun isExistingPaymentMethodRequired ()Ljava/lang/Boolean; public final fun isShippingAddressRequired ()Ljava/lang/Boolean; + public fun isSubmitButtonVisible ()Ljava/lang/Boolean; public fun writeToParcel (Landroid/os/Parcel;I)V } -public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : com/adyen/checkout/action/core/internal/ActionHandlingPaymentMethodConfigurationBuilder { +public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : com/adyen/checkout/action/core/internal/ActionHandlingPaymentMethodConfigurationBuilder, com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder { public fun (Landroid/content/Context;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V public fun (Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V public fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V @@ -154,6 +155,8 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : public final fun setMerchantInfo (Lcom/adyen/checkout/googlepay/MerchantInfo;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setShippingAddressParameters (Lcom/adyen/checkout/googlepay/ShippingAddressParameters;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setShippingAddressRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; + public synthetic fun setSubmitButtonVisible (Z)Lcom/adyen/checkout/components/core/internal/ButtonConfigurationBuilder; + public fun setSubmitButtonVisible (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setTotalPriceStatus (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 810dd88e0e..739664c573 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -96,6 +96,10 @@ class GooglePayComponent internal constructor( ?: adyenLog(AdyenLogLevel.ERROR) { "Component is currently not submittable, ignoring." } } + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } + } + override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false /** @@ -117,10 +121,6 @@ class GooglePayComponent internal constructor( googlePayDelegate.handleActivityResult(resultCode, data) } - override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } - } - override fun onCleared() { super.onCleared() adyenLog(AdyenLogLevel.DEBUG) { "onCleared" } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index b845ba3971..e6e094a0b0 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -53,6 +53,7 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.wallet.Wallet @@ -112,6 +113,7 @@ constructor( Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = order, @@ -203,6 +205,7 @@ constructor( Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = checkoutSession.order, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index dce829abcc..28aacb6c13 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -33,7 +33,9 @@ import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodMo import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper @@ -45,13 +47,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LongParameterList") internal class DefaultGooglePayDelegate( + private val submitHandler: SubmitHandler, private val observerRepository: PaymentObserverRepository, private val paymentMethod: PaymentMethod, private val order: OrderRequest?, @@ -66,8 +67,7 @@ internal class DefaultGooglePayDelegate( private val exceptionChannel: Channel = bufferedChannel() override val exceptionFlow: Flow = exceptionChannel.receiveAsFlow() - private val submitChannel: Channel = bufferedChannel() - override val submitFlow: Flow = submitChannel.receiveAsFlow() + override val submitFlow: Flow = submitHandler.submitFlow private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) override val viewFlow: Flow = _viewFlow @@ -80,12 +80,9 @@ internal class DefaultGooglePayDelegate( override fun initialize(coroutineScope: CoroutineScope) { _coroutineScope = coroutineScope + submitHandler.initialize(coroutineScope, componentStateFlow) initializeAnalytics(coroutineScope) - - componentStateFlow.onEach { - onState(it) - }.launchIn(coroutineScope) } private fun initializeAnalytics(coroutineScope: CoroutineScope) { @@ -96,15 +93,6 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } - private fun onState(state: GooglePayComponentState) { - if (state.isValid) { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - - submitChannel.trySend(state) - } - } - override fun observe( lifecycleOwner: LifecycleOwner, coroutineScope: CoroutineScope, @@ -155,7 +143,7 @@ internal class DefaultGooglePayDelegate( ) } - @Deprecated("Deprecated in favor of startGooglePayScreen()", replaceWith = ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of onSubmit()", replaceWith = ReplaceWith("onSubmit()")) override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) @@ -165,7 +153,7 @@ internal class DefaultGooglePayDelegate( } override fun onSubmit() { - adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } + adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { @@ -176,14 +164,8 @@ internal class DefaultGooglePayDelegate( override fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) { when (val statusCode = paymentDataTaskResult.status.statusCode) { CommonStatusCodes.SUCCESS -> { - val paymentData = paymentDataTaskResult.result - if (paymentData == null) { - adyenLog(AdyenLogLevel.ERROR) { "Result data is null" } - exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) - return - } adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } - updateComponentState(paymentData) + initiatePayment(paymentDataTaskResult.result) } CommonStatusCodes.CANCELED -> { @@ -218,8 +200,7 @@ internal class DefaultGooglePayDelegate( exceptionChannel.trySend(ComponentException("Result data is null")) return } - val paymentData = PaymentData.getFromIntent(data) - updateComponentState(paymentData) + initiatePayment(PaymentData.getFromIntent(data)) } Activity.RESULT_CANCELED -> { @@ -233,9 +214,22 @@ internal class DefaultGooglePayDelegate( val statusMessage: String = status?.let { ": ${it.statusMessage}" }.orEmpty() exceptionChannel.trySend(ComponentException("GooglePay returned an error$statusMessage")) } + } + } - else -> Unit + private fun initiatePayment(paymentData: PaymentData?) { + if (paymentData == null) { + adyenLog(AdyenLogLevel.ERROR) { "Payment data is null" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + return } + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } + + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + + updateComponentState(paymentData) + submitHandler.onSubmit(_componentStateFlow.value) } private fun trackThirdPartyErrorEvent() { @@ -255,10 +249,10 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } - // Currently, we don't show any button, but we could potentially show the Google Pay button. - override fun isConfirmationRequired(): Boolean = false + @Suppress("USELESS_IS_CHECK") + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType - override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt new file mode 100644 index 0000000000..4e3b1136bd --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 26/6/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.google.android.gms.wallet.button.ButtonConstants.ButtonType +import com.google.android.gms.wallet.button.ButtonOptions + +internal class GooglePayButtonView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : PayButton(context, attrs, defStyleAttr) { + + private val binding = ViewGooglePayButtonBinding.inflate(LayoutInflater.from(context), this) + + override fun initialize(delegate: ButtonDelegate) { + check(delegate is GooglePayDelegate) + + binding.payButton.initialize( + ButtonOptions.newBuilder() + .setButtonType(ButtonType.PAY) + .setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) + .build(), + ) + } + + override fun setEnabled(enabled: Boolean) { + binding.payButton.isEnabled = enabled + } + + override fun setOnClickListener(listener: OnClickListener?) { + binding.payButton.setOnClickListener(listener) + } + + override fun setText(text: String?) = Unit +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt index 1ca8a11d61..f6255bdb3d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -10,9 +10,12 @@ package com.adyen.checkout.googlepay.internal.ui import android.content.Context import android.view.LayoutInflater +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ButtonViewProvider import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider +import com.adyen.checkout.ui.core.internal.ui.view.PayButton internal object GooglePayViewProvider : ViewProvider { @@ -33,6 +36,14 @@ internal object GooglePayViewProvider : ViewProvider { } } -internal object GooglePayComponentViewType : ComponentViewType { +internal class GooglePayButtonViewProvider : ButtonViewProvider { + override fun getButton(context: Context): PayButton = GooglePayButtonView(context) +} + +internal object GooglePayComponentViewType : ButtonComponentViewType { + override val buttonViewProvider: ButtonViewProvider get() = GooglePayButtonViewProvider() + override val viewProvider: ViewProvider = GooglePayViewProvider + + override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index ded32f9d7c..cbecb9fabf 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -60,7 +60,7 @@ internal class GooglePayComponentParamsMapper( return GooglePayComponentParams( commonComponentParams = commonComponentParams, amount = commonComponentParams.amount ?: DEFAULT_AMOUNT, - isSubmitButtonVisible = googlePayConfiguration?.isSubmitButtonVisible ?: false, + isSubmitButtonVisible = shouldShowSubmitButton(commonComponentParams, googlePayConfiguration), gatewayMerchantId = googlePayConfiguration.getPreferredGatewayMerchantId(paymentMethod), allowedAuthMethods = googlePayConfiguration.getAvailableAuthMethods(), allowedCardNetworks = googlePayConfiguration.getAvailableCardNetworks(paymentMethod), @@ -80,6 +80,17 @@ internal class GooglePayComponentParamsMapper( ) } + private fun shouldShowSubmitButton( + commonComponentParams: CommonComponentParams, + googlePayConfiguration: GooglePayConfiguration?, + ): Boolean { + return if (commonComponentParams.isCreatedByDropIn) { + false + } else { + googlePayConfiguration?.isSubmitButtonVisible ?: false + } + } + /** * Returns the [GooglePayConfiguration.merchantAccount] if set, or falls back to the * paymentMethod.configuration.gatewayMerchantId field returned by the API. diff --git a/googlepay/src/main/res/layout/view_google_pay_button.xml b/googlepay/src/main/res/layout/view_google_pay_button.xml new file mode 100644 index 0000000000..28bb00cc96 --- /dev/null +++ b/googlepay/src/main/res/layout/view_google_pay_button.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index d900a59575..c0c5a3647c 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -22,12 +22,14 @@ import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManage import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment import com.adyen.checkout.core.exception.ComponentException +import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.extensions.test +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.google.android.gms.common.api.Status import com.google.android.gms.tasks.Tasks import com.google.android.gms.wallet.AutoResolveHelper @@ -60,6 +62,7 @@ import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MockitoExtension::class, LoggingExtension::class) internal class DefaultGooglePayDelegateTest( + @Mock private val submitHandler: SubmitHandler, @Mock private val paymentsClient: PaymentsClient, ) { @@ -68,6 +71,7 @@ internal class DefaultGooglePayDelegateTest( @BeforeEach fun beforeEach() { + whenever(paymentsClient.loadPaymentData(any())) doReturn Tasks.forResult(TEST_PAYMENT_DATA) analyticsManager = TestAnalyticsManager() delegate = createGooglePayDelegate() } @@ -182,16 +186,6 @@ internal class DefaultGooglePayDelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } - @Test - fun `when component state updates amd the data is valid, then submit event is tracked`() { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - delegate.updateComponentState(TEST_PAYMENT_DATA) - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) - } - @Test fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest { analyticsManager.setCheckoutAttemptId(TEST_CHECKOUT_ATTEMPT_ID) @@ -205,6 +199,18 @@ internal class DefaultGooglePayDelegateTest( } } + @Test + fun `when payment is successful and the data is valid, then submit event is tracked`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.updateComponentState(TEST_PAYMENT_DATA) + + val result = ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS) + delegate.handlePaymentResult(result) + + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() @@ -261,6 +267,7 @@ internal class DefaultGooglePayDelegateTest( ), ): DefaultGooglePayDelegate { return DefaultGooglePayDelegate( + submitHandler = submitHandler, observerRepository = PaymentObserverRepository(), paymentMethod = PaymentMethod(type = TEST_PAYMENT_METHOD_TYPE), order = TEST_ORDER, diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index 0054fef7e5..f8bf6d720d 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -174,7 +174,7 @@ internal class GooglePayComponentParamsMapperTest { currency = "CAD", value = 123L, ), - isSubmitButtonVisible = true, + isSubmitButtonVisible = false, ) assertEquals(expected, params) diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt index 8988f179d0..ac5a442fbd 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt @@ -147,6 +147,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) .buttonViewProvider.getButton(context) + buttonView.initialize(buttonDelegate) buttonView.setText(viewType, componentParams, localizedContext) buttonView.setOnClickListener { buttonDelegate.onSubmit() diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt index 96c12a09b2..a062e2567e 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.ui.core.databinding.DefaultPayButtonViewBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate internal class DefaultPayButton @JvmOverloads constructor( context: Context, @@ -21,6 +22,8 @@ internal class DefaultPayButton @JvmOverloads constructor( private val binding = DefaultPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + override fun initialize(delegate: ButtonDelegate) = Unit + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt index f818ba1070..452c93659c 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout import androidx.annotation.RestrictTo +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class PayButton( @@ -20,6 +21,8 @@ abstract class PayButton( defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { + abstract fun initialize(delegate: ButtonDelegate) + abstract override fun setEnabled(enabled: Boolean) abstract override fun setOnClickListener(listener: OnClickListener?) From c54dbba6abac7663407d21cbe89d2ac9b315908a Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:01:06 +0200 Subject: [PATCH 32/95] Support blocking interaction for GooglePayComponent COAND-942 --- .../checkout/googlepay/GooglePayComponent.kt | 4 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 4 +++ .../googlepay/GooglePayComponentTest.kt | 28 +++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 739664c573..450e0bb7f2 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -26,6 +26,7 @@ import com.adyen.checkout.components.core.internal.ui.ComponentDelegate import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider +import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType @@ -97,7 +98,8 @@ class GooglePayComponent internal constructor( } override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } + (delegate as? DefaultGooglePayDelegate)?.setInteractionBlocked(isInteractionBlocked) + ?: adyenLog(AdyenLogLevel.ERROR) { "Payment component is not interactable, ignoring." } } override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 28aacb6c13..2814b6b7e9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -254,6 +254,10 @@ internal class DefaultGooglePayDelegate( override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible + internal fun setInteractionBlocked(isInteractionBlocked: Boolean) { + submitHandler.setInteractionBlocked(isInteractionBlocked) + } + override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt index 6f59f59e13..f303433530 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt @@ -16,8 +16,8 @@ import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.GooglePayComponentViewType -import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -41,7 +41,7 @@ import org.mockito.kotlin.whenever @ExtendWith(MockitoExtension::class, TestDispatcherExtension::class, LoggingExtension::class) internal class GooglePayComponentTest( - @Mock private val googlePayDelegate: GooglePayDelegate, + @Mock private val googlePayDelegate: DefaultGooglePayDelegate, @Mock private val genericActionDelegate: GenericActionDelegate, @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, @Mock private val componentEventHandler: ComponentEventHandler, @@ -105,7 +105,7 @@ internal class GooglePayComponentTest( } @Test - fun `when cash app pay delegate view flow emits a value then component view flow should match that value`() = + fun `when google pay delegate view flow emits a value then component view flow should match that value`() = runTest { val delegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(googlePayDelegate.viewFlow) doReturn delegateViewFlow @@ -209,4 +209,26 @@ internal class GooglePayComponentTest( verify(googlePayDelegate).isConfirmationRequired() } + + @Test + fun `when setInteractionBlocked is called, then delegate is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.setInteractionBlocked(true) + + verify(googlePayDelegate).setInteractionBlocked(true) + } + + @Test + fun `when getGooglePayButtonParameters is called, then delegate is called`() { + component.getGooglePayButtonParameters() + + verify(googlePayDelegate).getGooglePayButtonParameters() + } } From da30c468d5bc8632646732a373c0c4c64cc5aecf Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:12:10 +0200 Subject: [PATCH 33/95] Add tests for GooglePayComponentParamsMapper COAND-942 --- .../model/GooglePayComponentParamsMapper.kt | 1 + .../GooglePayComponentParamsMapperTest.kt | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index cbecb9fabf..1b500e0475 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -87,6 +87,7 @@ internal class GooglePayComponentParamsMapper( return if (commonComponentParams.isCreatedByDropIn) { false } else { + // TODO return true by default in v6 googlePayConfiguration?.isSubmitButtonVisible ?: false } } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index f8bf6d720d..992a19543a 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -33,6 +33,9 @@ import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.test.LoggingExtension import com.google.android.gms.wallet.WalletConstants import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -483,6 +486,57 @@ internal class GooglePayComponentParamsMapperTest { assertEquals(expected, params) } + @Nested + inner class SubmitButtonVisibilityTest { + + @Test + fun `when created by drop-in, then submit button should not be visible`() { + val configuration = createCheckoutConfiguration() + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = DropInOverrideParams(null, null, true), + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertFalse(params.isSubmitButtonVisible) + } + + @Test + fun `when not created by drop-in and set in configuration, then submit button should be visible`() { + val configuration = createCheckoutConfiguration { + setSubmitButtonVisible(true) + } + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = null, + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertTrue(params.isSubmitButtonVisible) + } + + @Test + fun `when not created by drop-in and not configured, then submit button should not be visible`() { + val configuration = createCheckoutConfiguration() + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = null, + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertFalse(params.isSubmitButtonVisible) + } + } + private fun createCheckoutConfiguration( amount: Amount? = null, shopperLocale: Locale? = null, From 7ce752e20e848ec379044e48c8aaa7e5e01ef9d6 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:46:02 +0200 Subject: [PATCH 34/95] Add more tests for DefaultGooglePayDelegate COAND-942 --- .../ui/DefaultGooglePayDelegateTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index c0c5a3647c..628812e064 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -46,6 +46,7 @@ import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -56,6 +57,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Locale @@ -168,6 +170,51 @@ internal class DefaultGooglePayDelegateTest( } } + @Nested + @DisplayName("when submit button is configured to be") + inner class SubmitButtonVisibilityTest { + + @Test + fun `hidden, then it should not show`() { + delegate = createGooglePayDelegate( + configuration = createCheckoutConfiguration { + setSubmitButtonVisible(false) + }, + ) + + assertFalse(delegate.shouldShowSubmitButton()) + } + + @Test + fun `visible, then it should show`() { + delegate = createGooglePayDelegate( + configuration = createCheckoutConfiguration { + setSubmitButtonVisible(true) + }, + ) + + assertTrue(delegate.shouldShowSubmitButton()) + } + } + + @Nested + inner class SubmitHandlerTest { + + @Test + fun `when delegate is initialized, then submit handler event is initialized`() = runTest { + val coroutineScope = CoroutineScope(UnconfinedTestDispatcher()) + delegate.initialize(coroutineScope) + verify(submitHandler).initialize(coroutineScope, delegate.componentStateFlow) + } + + @Test + fun `when delegate setInteractionBlocked is called, then submit handler setInteractionBlocked is called`() = + runTest { + delegate.setInteractionBlocked(true) + verify(submitHandler).setInteractionBlocked(true) + } + } + @Nested inner class AnalyticsTest { From 983a2ba4522c0b90e0631685850c386ec18cd22e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 11 Oct 2024 12:42:02 +0200 Subject: [PATCH 35/95] Allow to programmatically style the Google Pay button COAND-942 --- .../googlepay/GooglePayButtonStyling.kt | 41 +++++++++++++++++++ .../googlepay/GooglePayConfiguration.kt | 8 ++++ .../internal/ui/GooglePayButtonView.kt | 28 ++++++++++--- .../internal/ui/GooglePayDelegate.kt | 3 ++ .../ui/model/GooglePayComponentParams.kt | 2 + .../model/GooglePayComponentParamsMapper.kt | 1 + .../GooglePayComponentParamsMapperTest.kt | 12 ++++++ .../internal/util/GooglePayUtilsTest.kt | 2 + 8 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt new file mode 100644 index 0000000000..b136440742 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 11/10/2024. + */ + +package com.adyen.checkout.googlepay + +import android.os.Parcelable +import androidx.annotation.Dimension +import com.google.android.gms.wallet.button.ButtonConstants +import kotlinx.parcelize.Parcelize + +@Parcelize +data class GooglePayButtonStyling( + val buttonTheme: GooglePayButtonTheme?, + val buttonType: GooglePayButtonType?, + @Dimension(Dimension.DP) val cornerRadius: Int?, +) : Parcelable + +enum class GooglePayButtonTheme( + val value: Int, +) { + LIGHT(ButtonConstants.ButtonTheme.LIGHT), + DARK(ButtonConstants.ButtonTheme.DARK), +} + +enum class GooglePayButtonType( + val value: Int, +) { + BUY(ButtonConstants.ButtonType.BUY), + BOOK(ButtonConstants.ButtonType.BOOK), + CHECKOUT(ButtonConstants.ButtonType.CHECKOUT), + DONATE(ButtonConstants.ButtonType.DONATE), + ORDER(ButtonConstants.ButtonType.ORDER), + PAY(ButtonConstants.ButtonType.PAY), + SUBSCRIBE(ButtonConstants.ButtonType.SUBSCRIBE), + PLAIN(ButtonConstants.ButtonType.PLAIN), +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt index 5593701902..e0bbef6a2a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt @@ -54,6 +54,7 @@ class GooglePayConfiguration private constructor( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean?, val billingAddressParameters: BillingAddressParameters?, + val googlePayButtonStyling: GooglePayButtonStyling?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -80,6 +81,7 @@ class GooglePayConfiguration private constructor( private var isBillingAddressRequired: Boolean? = null private var billingAddressParameters: BillingAddressParameters? = null private var totalPriceStatus: String? = null + private var googlePayButtonStyling: GooglePayButtonStyling? = null private var isSubmitButtonVisible: Boolean? = null /** @@ -383,6 +385,11 @@ class GooglePayConfiguration private constructor( return super.setAmount(amount) } + fun setGooglePayButtonStyling(googlePayButtonStyling: GooglePayButtonStyling): Builder { + this.googlePayButtonStyling = googlePayButtonStyling + return this + } + /** * Sets if submit button will be visible or not. * @@ -419,6 +426,7 @@ class GooglePayConfiguration private constructor( shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = isBillingAddressRequired, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, genericActionConfiguration = genericActionConfigurationBuilder.build(), ) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 4e3b1136bd..20b05c3fca 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -9,12 +9,12 @@ package com.adyen.checkout.googlepay.internal.ui import android.content.Context +import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton -import com.google.android.gms.wallet.button.ButtonConstants.ButtonType import com.google.android.gms.wallet.button.ButtonOptions internal class GooglePayButtonView @JvmOverloads constructor( @@ -28,11 +28,29 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun initialize(delegate: ButtonDelegate) { check(delegate is GooglePayDelegate) + val buttonStyle = delegate.componentParams.googlePayButtonStyling + + val buttonType = buttonStyle?.buttonType + val buttonTheme = buttonStyle?.buttonTheme + val cornerRadius = + buttonStyle?.cornerRadius?.let { (it * Resources.getSystem().displayMetrics.density).toInt() } + binding.payButton.initialize( - ButtonOptions.newBuilder() - .setButtonType(ButtonType.PAY) - .setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) - .build(), + ButtonOptions.newBuilder().apply { + if (buttonType != null) { + setButtonType(buttonType.value) + } + + if (buttonTheme != null) { + setButtonTheme(buttonTheme.value) + } + + if (cornerRadius != null) { + setCornerRadius(cornerRadius) + } + + setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) + }.build(), ) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 77ee7a403e..309a96d08d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -14,6 +14,7 @@ import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task @@ -26,6 +27,8 @@ internal interface GooglePayDelegate : ViewProvidingDelegate, ButtonDelegate { + override val componentParams: GooglePayComponentParams + val componentStateFlow: Flow val exceptionFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt index b09994cc08..f6d8bed96a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt @@ -13,6 +13,7 @@ import com.adyen.checkout.components.core.internal.ui.model.ButtonParams import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.googlepay.BillingAddressParameters +import com.adyen.checkout.googlepay.GooglePayButtonStyling import com.adyen.checkout.googlepay.MerchantInfo import com.adyen.checkout.googlepay.ShippingAddressParameters @@ -36,4 +37,5 @@ internal data class GooglePayComponentParams( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean, val billingAddressParameters: BillingAddressParameters?, + val googlePayButtonStyling: GooglePayButtonStyling?, ) : ComponentParams by commonComponentParams, ButtonParams diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index 1b500e0475..02e974e9dd 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -77,6 +77,7 @@ internal class GooglePayComponentParamsMapper( shippingAddressParameters = googlePayConfiguration?.shippingAddressParameters, isBillingAddressRequired = googlePayConfiguration?.isBillingAddressRequired ?: false, billingAddressParameters = googlePayConfiguration?.billingAddressParameters, + googlePayButtonStyling = googlePayConfiguration?.googlePayButtonStyling, ) } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index 992a19543a..ec08dceec0 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -26,6 +26,9 @@ import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.AllowedAuthMethods import com.adyen.checkout.googlepay.AllowedCardNetworks import com.adyen.checkout.googlepay.BillingAddressParameters +import com.adyen.checkout.googlepay.GooglePayButtonStyling +import com.adyen.checkout.googlepay.GooglePayButtonTheme +import com.adyen.checkout.googlepay.GooglePayButtonType import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.MerchantInfo import com.adyen.checkout.googlepay.ShippingAddressParameters @@ -74,6 +77,11 @@ internal class GooglePayComponentParamsMapperTest { val allowedCardNetworks = listOf("CARD1", "CARD2") val shippingAddressParameters = ShippingAddressParameters(listOf("ZZ", "AA"), true) val billingAddressParameters = BillingAddressParameters("FORMAT", true) + val googlePayButtonStyling = GooglePayButtonStyling( + buttonTheme = GooglePayButtonTheme.LIGHT, + buttonType = GooglePayButtonType.BOOK, + cornerRadius = 16, + ) val configuration = CheckoutConfiguration( shopperLocale = Locale.FRANCE, @@ -98,6 +106,7 @@ internal class GooglePayComponentParamsMapperTest { setShippingAddressParameters(shippingAddressParameters) setShippingAddressRequired(true) setTotalPriceStatus("STATUS") + setGooglePayButtonStyling(googlePayButtonStyling) } } @@ -132,6 +141,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = true, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, ) assertEquals(expected, params) @@ -599,6 +609,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters: ShippingAddressParameters? = null, isBillingAddressRequired: Boolean = false, billingAddressParameters: BillingAddressParameters? = null, + googlePayButtonStyling: GooglePayButtonStyling? = null, ) = GooglePayComponentParams( commonComponentParams = CommonComponentParams( shopperLocale = shopperLocale, @@ -626,6 +637,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = isBillingAddressRequired, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, ) companion object { diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt index 670bb52e00..4ef595c7e2 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt @@ -265,6 +265,7 @@ internal class GooglePayUtilsTest { shippingAddressParameters = null, isBillingAddressRequired = false, billingAddressParameters = null, + googlePayButtonStyling = null, ) } @@ -305,6 +306,7 @@ internal class GooglePayUtilsTest { format = "FORMAT", isPhoneNumberRequired = true, ), + googlePayButtonStyling = null, ) } } From 33b5b59d088686df7cec933d7e67675b736b579f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 11 Oct 2024 14:17:20 +0200 Subject: [PATCH 36/95] Allow styling of Google Pay button through XML COAND-942 --- .../src/main/res/values-night/styles.xml | 4 +- example-app/src/main/res/values/styles.xml | 4 +- .../googlepay/GooglePayButtonStyling.kt | 6 +-- .../internal/ui/GooglePayButtonView.kt | 53 ++++++++++++++++++- .../res/layout/view_google_pay_button.xml | 1 + googlepay/src/main/res/values/attrs.xml | 30 +++++++++++ googlepay/src/main/res/values/styles.xml | 15 ++++++ 7 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 googlepay/src/main/res/values/attrs.xml create mode 100644 googlepay/src/main/res/values/styles.xml diff --git a/example-app/src/main/res/values-night/styles.xml b/example-app/src/main/res/values-night/styles.xml index b8704d3158..f347af9167 100644 --- a/example-app/src/main/res/values-night/styles.xml +++ b/example-app/src/main/res/values-night/styles.xml @@ -8,7 +8,7 @@ - diff --git a/example-app/src/main/res/values/styles.xml b/example-app/src/main/res/values/styles.xml index ef0d1e9681..41c372fcf1 100644 --- a/example-app/src/main/res/values/styles.xml +++ b/example-app/src/main/res/values/styles.xml @@ -38,8 +38,8 @@ diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt index b136440742..79c5bdd5a4 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -15,9 +15,9 @@ import kotlinx.parcelize.Parcelize @Parcelize data class GooglePayButtonStyling( - val buttonTheme: GooglePayButtonTheme?, - val buttonType: GooglePayButtonType?, - @Dimension(Dimension.DP) val cornerRadius: Int?, + val buttonTheme: GooglePayButtonTheme? = null, + val buttonType: GooglePayButtonType? = null, + @Dimension(Dimension.DP) val cornerRadius: Int? = null, ) : Parcelable enum class GooglePayButtonTheme( diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 20b05c3fca..ae5208a483 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -12,6 +12,9 @@ import android.content.Context import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater +import com.adyen.checkout.googlepay.GooglePayButtonTheme +import com.adyen.checkout.googlepay.GooglePayButtonType +import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton @@ -25,15 +28,37 @@ internal class GooglePayButtonView @JvmOverloads constructor( private val binding = ViewGooglePayButtonBinding.inflate(LayoutInflater.from(context), this) + private val styledButtonType: GooglePayButtonType? + private val styledButtonTheme: GooglePayButtonTheme? + private val styledCornerRadius: Int? + + init { + val typedArray = context.theme.obtainStyledAttributes( + attrs, + R.styleable.GooglePayButtonView, + defStyleAttr, + R.style.AdyenCheckout_GooglePay_Button, + ) + styledButtonType = + typedArray.getInt(R.styleable.GooglePayButtonView_adyenGooglePayButtonType, -1).mapStyledButtonType() + styledButtonTheme = + typedArray.getInt(R.styleable.GooglePayButtonView_adyenGooglePayButtonTheme, -1).mapStyledButtonTheme() + styledCornerRadius = + typedArray.getDimensionPixelSize(R.styleable.GooglePayButtonView_adyenGooglePayButtonCornerRadius, -1) + .mapStyledCornerRadius() + typedArray.recycle() + } + override fun initialize(delegate: ButtonDelegate) { check(delegate is GooglePayDelegate) val buttonStyle = delegate.componentParams.googlePayButtonStyling - val buttonType = buttonStyle?.buttonType - val buttonTheme = buttonStyle?.buttonTheme + val buttonType = buttonStyle?.buttonType ?: styledButtonType + val buttonTheme = buttonStyle?.buttonTheme ?: styledButtonTheme val cornerRadius = buttonStyle?.cornerRadius?.let { (it * Resources.getSystem().displayMetrics.density).toInt() } + ?: styledCornerRadius binding.payButton.initialize( ButtonOptions.newBuilder().apply { @@ -63,4 +88,28 @@ internal class GooglePayButtonView @JvmOverloads constructor( } override fun setText(text: String?) = Unit + + private fun Int.mapStyledButtonType(): GooglePayButtonType? = when (this) { + 0 -> GooglePayButtonType.BUY + 1 -> GooglePayButtonType.BOOK + 2 -> GooglePayButtonType.CHECKOUT + 3 -> GooglePayButtonType.DONATE + 4 -> GooglePayButtonType.ORDER + 5 -> GooglePayButtonType.PAY + 6 -> GooglePayButtonType.SUBSCRIBE + 7 -> GooglePayButtonType.PLAIN + else -> null + } + + private fun Int.mapStyledButtonTheme(): GooglePayButtonTheme? = when (this) { + 0 -> GooglePayButtonTheme.LIGHT + 1 -> GooglePayButtonTheme.DARK + else -> null + } + + private fun Int.mapStyledCornerRadius(): Int? = if (this == -1) { + null + } else { + this + } } diff --git a/googlepay/src/main/res/layout/view_google_pay_button.xml b/googlepay/src/main/res/layout/view_google_pay_button.xml index 28bb00cc96..5adc62558e 100644 --- a/googlepay/src/main/res/layout/view_google_pay_button.xml +++ b/googlepay/src/main/res/layout/view_google_pay_button.xml @@ -12,6 +12,7 @@ diff --git a/googlepay/src/main/res/values/attrs.xml b/googlepay/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..4239a03f37 --- /dev/null +++ b/googlepay/src/main/res/values/attrs.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/googlepay/src/main/res/values/styles.xml b/googlepay/src/main/res/values/styles.xml new file mode 100644 index 0000000000..61e560dbb9 --- /dev/null +++ b/googlepay/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index 440414a733..a56954af9c 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -65,6 +65,58 @@ public final class com/adyen/checkout/googlepay/GooglePayButtonParameters { public fun toString ()Ljava/lang/String; } +public final class com/adyen/checkout/googlepay/GooglePayButtonStyling : android/os/Parcelable { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun ()V + public fun (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;)V + public synthetic fun (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public final fun component2 ()Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public static synthetic fun copy$default (Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getButtonTheme ()Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public final fun getButtonType ()Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public final fun getCornerRadius ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonStyling$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonTheme : java/lang/Enum { + public static final field DARK Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static final field LIGHT Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static fun values ()[Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonType : java/lang/Enum { + public static final field BOOK Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field BUY Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field CHECKOUT Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field DONATE Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field ORDER Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field PAY Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field PLAIN Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field SUBSCRIBE Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static fun values ()[Lcom/adyen/checkout/googlepay/GooglePayButtonType; +} + public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/ButtonComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { public static final field Companion Lcom/adyen/checkout/googlepay/GooglePayComponent$Companion; public static final field PAYMENT_METHOD_TYPES Ljava/util/List; @@ -106,7 +158,7 @@ public final class com/adyen/checkout/googlepay/GooglePayComponentState : com/ad public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/adyen/checkout/components/core/internal/ButtonConfiguration, com/adyen/checkout/components/core/internal/Configuration { public static final field CREATOR Landroid/os/Parcelable$Creator; - public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun describeContents ()I public final fun getAllowedAuthMethods ()Ljava/util/List; public final fun getAllowedCardNetworks ()Ljava/util/List; @@ -116,6 +168,7 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/ady public fun getClientKey ()Ljava/lang/String; public final fun getCountryCode ()Ljava/lang/String; public fun getEnvironment ()Lcom/adyen/checkout/core/Environment; + public final fun getGooglePayButtonStyling ()Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; public final fun getGooglePayEnvironment ()Ljava/lang/Integer; public final fun getMerchantAccount ()Ljava/lang/String; public final fun getMerchantInfo ()Lcom/adyen/checkout/googlepay/MerchantInfo; @@ -150,6 +203,7 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : public final fun setCountryCode (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setEmailRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setExistingPaymentMethodRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; + public final fun setGooglePayButtonStyling (Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setGooglePayEnvironment (I)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setMerchantAccount (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setMerchantInfo (Lcom/adyen/checkout/googlepay/MerchantInfo;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt index fe2ac968b3..4d48323a70 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -20,6 +20,7 @@ import kotlinx.parcelize.Parcelize * @param buttonType Changes the text displayed inside of the button. * @param cornerRadius Sets the corner radius of the button. For example, passing 16 means the radius will be 16 dp. */ +@Suppress("MaxLineLength") @Parcelize data class GooglePayButtonStyling( val buttonTheme: GooglePayButtonTheme? = null, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index ae5208a483..6b99b440b6 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -89,6 +89,7 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun setText(text: String?) = Unit + @Suppress("MagicNumber") private fun Int.mapStyledButtonType(): GooglePayButtonType? = when (this) { 0 -> GooglePayButtonType.BUY 1 -> GooglePayButtonType.BOOK From e35f80b114b2f884973b31114bdf48a9708bda84 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 15 Oct 2024 15:58:54 +0200 Subject: [PATCH 39/95] Automatically use dark/light button theme based on app theme This is the same as we do with other payment method buttons and makes it easier for merchants to supports dark-mode. It is still be possible for merchants to change this behaviour if they want. COAND-942 --- example-app/src/main/res/values/styles.xml | 4 ---- .../src/main/res/values-night/styles.xml | 5 +++-- googlepay/src/main/res/values/styles.xml | 4 +++- 3 files changed, 6 insertions(+), 7 deletions(-) rename {example-app => googlepay}/src/main/res/values-night/styles.xml (81%) diff --git a/example-app/src/main/res/values/styles.xml b/example-app/src/main/res/values/styles.xml index acbb8ba49a..ec5f9b5a42 100644 --- a/example-app/src/main/res/values/styles.xml +++ b/example-app/src/main/res/values/styles.xml @@ -38,8 +38,4 @@ - diff --git a/example-app/src/main/res/values-night/styles.xml b/googlepay/src/main/res/values-night/styles.xml similarity index 81% rename from example-app/src/main/res/values-night/styles.xml rename to googlepay/src/main/res/values-night/styles.xml index f347af9167..ad24e59f4c 100644 --- a/example-app/src/main/res/values-night/styles.xml +++ b/googlepay/src/main/res/values-night/styles.xml @@ -1,9 +1,9 @@ @@ -11,4 +11,5 @@ + diff --git a/googlepay/src/main/res/values/styles.xml b/googlepay/src/main/res/values/styles.xml index 61e560dbb9..afba028b53 100644 --- a/googlepay/src/main/res/values/styles.xml +++ b/googlepay/src/main/res/values/styles.xml @@ -10,6 +10,8 @@ From 494c54f97134a86fff6027573075afaf0ca5fc3e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 15 Oct 2024 13:38:39 +0200 Subject: [PATCH 40/95] Display toolbar in drop-in Google Pay fragment COAND-1012 --- .../ui/GooglePayComponentDialogFragment.kt | 16 ++++++++++++++++ .../res/layout/fragment_google_pay_component.xml | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt index 8d0abe3465..b4b0fdfa33 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt @@ -42,6 +42,12 @@ internal class GooglePayComponentDialogFragment : private lateinit var paymentMethod: PaymentMethod private lateinit var component: GooglePayComponent + private val toolbarMode: DropInBottomSheetToolbarMode + get() = when { + dropInViewModel.shouldSkipToSinglePaymentMethod() -> DropInBottomSheetToolbarMode.CLOSE_BUTTON + else -> DropInBottomSheetToolbarMode.BACK_BUTTON + } + override fun onCreate(savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onCreate" } super.onCreate(savedInstanceState) @@ -60,6 +66,8 @@ internal class GooglePayComponentDialogFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onViewCreated" } + initToolbar() + loadComponent() binding.componentView.attach(component, viewLifecycleOwner) @@ -72,6 +80,14 @@ internal class GooglePayComponentDialogFragment : .launchIn(viewLifecycleOwner.lifecycleScope) } + private fun initToolbar() = with(binding.bottomSheetToolbar) { + setTitle(paymentMethod.name) + setOnButtonClickListener { + onBackPressed() + } + setMode(toolbarMode) + } + private fun loadComponent() { @Suppress("SwallowedException") try { diff --git a/drop-in/src/main/res/layout/fragment_google_pay_component.xml b/drop-in/src/main/res/layout/fragment_google_pay_component.xml index 32cee25328..59b9c0ff67 100644 --- a/drop-in/src/main/res/layout/fragment_google_pay_component.xml +++ b/drop-in/src/main/res/layout/fragment_google_pay_component.xml @@ -11,6 +11,11 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + Date: Tue, 15 Oct 2024 15:49:25 +0200 Subject: [PATCH 41/95] Add release note COAND-1012 --- RELEASE_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f6d200830b..fdcfd2a392 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,7 @@ ## New - Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. +- For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. ## Fixed From 6ab072d4d0a6d64af8ab028cf2cf29cba2f8e5f3 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 16 Oct 2024 15:37:13 +0200 Subject: [PATCH 42/95] Move Cash apps loading indicator to ui-core COAND-1012 --- .../internal/ui/CashAppPayViewProvider.kt | 4 +-- .../main/res/template/values/strings.xml.tt | 1 - cashapppay/src/main/res/values/styles.xml | 13 --------- .../internal/ui/view/ProcessingPaymentView.kt | 27 ++++++++++++------- .../res/layout/processing_payment_view.xml | 4 +-- .../main/res/template/values/strings.xml.tt | 2 ++ ui-core/src/main/res/values/strings.xml | 2 ++ ui-core/src/main/res/values/styles.xml | 15 +++++++++++ 8 files changed, 41 insertions(+), 27 deletions(-) rename cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt => ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt (61%) rename cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml => ui-core/src/main/res/layout/processing_payment_view.xml (80%) diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt index de259f016b..a8f667decc 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt @@ -11,13 +11,13 @@ package com.adyen.checkout.cashapppay.internal.ui import android.content.Context import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayButtonView import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayView -import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayWaitingView import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonViewProvider import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView internal object CashAppPayViewProvider : ViewProvider { @@ -26,7 +26,7 @@ internal object CashAppPayViewProvider : ViewProvider { context: Context, ): ComponentView = when (viewType) { CashAppPayComponentViewType -> CashAppPayView(context) - PaymentInProgressViewType -> CashAppPayWaitingView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/cashapppay/src/main/res/template/values/strings.xml.tt b/cashapppay/src/main/res/template/values/strings.xml.tt index 3ce489f054..b936f73e1d 100644 --- a/cashapppay/src/main/res/template/values/strings.xml.tt +++ b/cashapppay/src/main/res/template/values/strings.xml.tt @@ -8,5 +8,4 @@ %%storeDetails%% - %%paypal.processingPayment%% diff --git a/cashapppay/src/main/res/values/styles.xml b/cashapppay/src/main/res/values/styles.xml index 643a771043..52a4547201 100644 --- a/cashapppay/src/main/res/values/styles.xml +++ b/cashapppay/src/main/res/values/styles.xml @@ -15,18 +15,5 @@ 18sp - - - - + + + + From 5c04063cb15aa00cfa1141b1822c8f236e102b58 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 16 Oct 2024 16:30:07 +0200 Subject: [PATCH 43/95] Add translations COAND-1012 --- cashapppay/src/main/res/values-ar/strings.xml | 1 - cashapppay/src/main/res/values-bg-rBG/strings.xml | 1 - cashapppay/src/main/res/values-ca-rES/strings.xml | 1 - cashapppay/src/main/res/values-cs-rCZ/strings.xml | 1 - cashapppay/src/main/res/values-da-rDK/strings.xml | 1 - cashapppay/src/main/res/values-de-rDE/strings.xml | 1 - cashapppay/src/main/res/values-el-rGR/strings.xml | 1 - cashapppay/src/main/res/values-es-rES/strings.xml | 1 - cashapppay/src/main/res/values-et-rEE/strings.xml | 1 - cashapppay/src/main/res/values-fi-rFI/strings.xml | 1 - cashapppay/src/main/res/values-fr-rFR/strings.xml | 1 - cashapppay/src/main/res/values-hr-rHR/strings.xml | 1 - cashapppay/src/main/res/values-hu-rHU/strings.xml | 1 - cashapppay/src/main/res/values-is-rIS/strings.xml | 1 - cashapppay/src/main/res/values-it-rIT/strings.xml | 1 - cashapppay/src/main/res/values-ja-rJP/strings.xml | 1 - cashapppay/src/main/res/values-ko-rKR/strings.xml | 1 - cashapppay/src/main/res/values-lt-rLT/strings.xml | 1 - cashapppay/src/main/res/values-lv-rLV/strings.xml | 1 - cashapppay/src/main/res/values-nb-rNO/strings.xml | 1 - cashapppay/src/main/res/values-nl-rNL/strings.xml | 1 - cashapppay/src/main/res/values-pl-rPL/strings.xml | 1 - cashapppay/src/main/res/values-pt-rBR/strings.xml | 1 - cashapppay/src/main/res/values-pt-rPT/strings.xml | 1 - cashapppay/src/main/res/values-ro-rRO/strings.xml | 1 - cashapppay/src/main/res/values-ru-rRU/strings.xml | 1 - cashapppay/src/main/res/values-sk-rSK/strings.xml | 1 - cashapppay/src/main/res/values-sl-rSI/strings.xml | 1 - cashapppay/src/main/res/values-sv-rSE/strings.xml | 1 - cashapppay/src/main/res/values-zh-rCN/strings.xml | 1 - cashapppay/src/main/res/values-zh-rTW/strings.xml | 1 - cashapppay/src/main/res/values/strings.xml | 1 - twint/src/main/res/values-ar/strings.xml | 2 +- twint/src/main/res/values-bg-rBG/strings.xml | 2 +- twint/src/main/res/values-ca-rES/strings.xml | 2 +- twint/src/main/res/values-cs-rCZ/strings.xml | 2 +- twint/src/main/res/values-da-rDK/strings.xml | 2 +- twint/src/main/res/values-de-rDE/strings.xml | 2 +- twint/src/main/res/values-el-rGR/strings.xml | 2 +- twint/src/main/res/values-es-rES/strings.xml | 2 +- twint/src/main/res/values-et-rEE/strings.xml | 2 +- twint/src/main/res/values-fi-rFI/strings.xml | 2 +- twint/src/main/res/values-fr-rFR/strings.xml | 2 +- twint/src/main/res/values-hr-rHR/strings.xml | 2 +- twint/src/main/res/values-hu-rHU/strings.xml | 2 +- twint/src/main/res/values-is-rIS/strings.xml | 2 +- twint/src/main/res/values-it-rIT/strings.xml | 2 +- twint/src/main/res/values-ja-rJP/strings.xml | 2 +- twint/src/main/res/values-ko-rKR/strings.xml | 2 +- twint/src/main/res/values-lt-rLT/strings.xml | 2 +- twint/src/main/res/values-lv-rLV/strings.xml | 2 +- twint/src/main/res/values-nb-rNO/strings.xml | 2 +- twint/src/main/res/values-nl-rNL/strings.xml | 2 +- twint/src/main/res/values-pl-rPL/strings.xml | 2 +- twint/src/main/res/values-pt-rBR/strings.xml | 2 +- twint/src/main/res/values-pt-rPT/strings.xml | 2 +- twint/src/main/res/values-ro-rRO/strings.xml | 2 +- twint/src/main/res/values-ru-rRU/strings.xml | 2 +- twint/src/main/res/values-sk-rSK/strings.xml | 2 +- twint/src/main/res/values-sl-rSI/strings.xml | 2 +- twint/src/main/res/values-sv-rSE/strings.xml | 2 +- twint/src/main/res/values-zh-rCN/strings.xml | 2 +- twint/src/main/res/values-zh-rTW/strings.xml | 2 +- twint/src/main/res/values/strings.xml | 2 +- ui-core/src/main/res/values-ar/strings.xml | 2 ++ ui-core/src/main/res/values-bg-rBG/strings.xml | 2 ++ ui-core/src/main/res/values-ca-rES/strings.xml | 2 ++ ui-core/src/main/res/values-cs-rCZ/strings.xml | 2 ++ ui-core/src/main/res/values-da-rDK/strings.xml | 2 ++ ui-core/src/main/res/values-de-rDE/strings.xml | 2 ++ ui-core/src/main/res/values-el-rGR/strings.xml | 2 ++ ui-core/src/main/res/values-es-rES/strings.xml | 2 ++ ui-core/src/main/res/values-et-rEE/strings.xml | 2 ++ ui-core/src/main/res/values-fi-rFI/strings.xml | 2 ++ ui-core/src/main/res/values-fr-rFR/strings.xml | 2 ++ ui-core/src/main/res/values-hr-rHR/strings.xml | 2 ++ ui-core/src/main/res/values-hu-rHU/strings.xml | 2 ++ ui-core/src/main/res/values-is-rIS/strings.xml | 2 ++ ui-core/src/main/res/values-it-rIT/strings.xml | 2 ++ ui-core/src/main/res/values-ja-rJP/strings.xml | 2 ++ ui-core/src/main/res/values-ko-rKR/strings.xml | 2 ++ ui-core/src/main/res/values-lt-rLT/strings.xml | 2 ++ ui-core/src/main/res/values-lv-rLV/strings.xml | 2 ++ ui-core/src/main/res/values-nb-rNO/strings.xml | 2 ++ ui-core/src/main/res/values-nl-rNL/strings.xml | 2 ++ ui-core/src/main/res/values-pl-rPL/strings.xml | 2 ++ ui-core/src/main/res/values-pt-rBR/strings.xml | 2 ++ ui-core/src/main/res/values-pt-rPT/strings.xml | 2 ++ ui-core/src/main/res/values-ro-rRO/strings.xml | 2 ++ ui-core/src/main/res/values-ru-rRU/strings.xml | 2 ++ ui-core/src/main/res/values-sk-rSK/strings.xml | 2 ++ ui-core/src/main/res/values-sl-rSI/strings.xml | 2 ++ ui-core/src/main/res/values-sv-rSE/strings.xml | 2 ++ ui-core/src/main/res/values-zh-rCN/strings.xml | 2 ++ ui-core/src/main/res/values-zh-rTW/strings.xml | 2 ++ 95 files changed, 94 insertions(+), 64 deletions(-) diff --git a/cashapppay/src/main/res/values-ar/strings.xml b/cashapppay/src/main/res/values-ar/strings.xml index 2b812ea78f..b0878541df 100644 --- a/cashapppay/src/main/res/values-ar/strings.xml +++ b/cashapppay/src/main/res/values-ar/strings.xml @@ -8,5 +8,4 @@ حفظ لمدفوعاتي القادمة - جارِ معالجة المدفوعات… diff --git a/cashapppay/src/main/res/values-bg-rBG/strings.xml b/cashapppay/src/main/res/values-bg-rBG/strings.xml index 8f84adb4cf..49f695af31 100644 --- a/cashapppay/src/main/res/values-bg-rBG/strings.xml +++ b/cashapppay/src/main/res/values-bg-rBG/strings.xml @@ -8,5 +8,4 @@ Запазване за следващото ми плащане - Обработка на плащането… diff --git a/cashapppay/src/main/res/values-ca-rES/strings.xml b/cashapppay/src/main/res/values-ca-rES/strings.xml index 0634aeb780..b638f6b993 100644 --- a/cashapppay/src/main/res/values-ca-rES/strings.xml +++ b/cashapppay/src/main/res/values-ca-rES/strings.xml @@ -8,5 +8,4 @@ Desa\'l per al meu proper pagament - S\'esta processant el pagament… diff --git a/cashapppay/src/main/res/values-cs-rCZ/strings.xml b/cashapppay/src/main/res/values-cs-rCZ/strings.xml index 1801d976d9..4308eac985 100644 --- a/cashapppay/src/main/res/values-cs-rCZ/strings.xml +++ b/cashapppay/src/main/res/values-cs-rCZ/strings.xml @@ -8,5 +8,4 @@ Uložit pro příští platby - Zpracování platby… diff --git a/cashapppay/src/main/res/values-da-rDK/strings.xml b/cashapppay/src/main/res/values-da-rDK/strings.xml index b5e13d4b70..7e5cefdd1b 100644 --- a/cashapppay/src/main/res/values-da-rDK/strings.xml +++ b/cashapppay/src/main/res/values-da-rDK/strings.xml @@ -8,5 +8,4 @@ Gem til min næste betaling - Behandler betaling… diff --git a/cashapppay/src/main/res/values-de-rDE/strings.xml b/cashapppay/src/main/res/values-de-rDE/strings.xml index 1a33495c92..1ff4ddc1b4 100644 --- a/cashapppay/src/main/res/values-de-rDE/strings.xml +++ b/cashapppay/src/main/res/values-de-rDE/strings.xml @@ -8,5 +8,4 @@ Für zukünftige Zahlvorgänge speichern - Zahlung wird verarbeitet… diff --git a/cashapppay/src/main/res/values-el-rGR/strings.xml b/cashapppay/src/main/res/values-el-rGR/strings.xml index c3da340648..2b59f67998 100644 --- a/cashapppay/src/main/res/values-el-rGR/strings.xml +++ b/cashapppay/src/main/res/values-el-rGR/strings.xml @@ -8,5 +8,4 @@ Αποθήκευση για την επόμενη πληρωμή μου - Επεξεργασία πληρωμής… diff --git a/cashapppay/src/main/res/values-es-rES/strings.xml b/cashapppay/src/main/res/values-es-rES/strings.xml index a282fe5af7..79fef68395 100644 --- a/cashapppay/src/main/res/values-es-rES/strings.xml +++ b/cashapppay/src/main/res/values-es-rES/strings.xml @@ -8,5 +8,4 @@ Recordar para mi próximo pago - Procesando pago… diff --git a/cashapppay/src/main/res/values-et-rEE/strings.xml b/cashapppay/src/main/res/values-et-rEE/strings.xml index b37ef99f09..52ae2947aa 100644 --- a/cashapppay/src/main/res/values-et-rEE/strings.xml +++ b/cashapppay/src/main/res/values-et-rEE/strings.xml @@ -8,5 +8,4 @@ Salvesta mu järgmise makse jaoks - Makse töötlemine … diff --git a/cashapppay/src/main/res/values-fi-rFI/strings.xml b/cashapppay/src/main/res/values-fi-rFI/strings.xml index 47a7aebdc4..27ad6df965 100644 --- a/cashapppay/src/main/res/values-fi-rFI/strings.xml +++ b/cashapppay/src/main/res/values-fi-rFI/strings.xml @@ -8,5 +8,4 @@ Tallenna seuraavaa maksuani varten - Maksua käsitellään… diff --git a/cashapppay/src/main/res/values-fr-rFR/strings.xml b/cashapppay/src/main/res/values-fr-rFR/strings.xml index 6615d2c753..aae6b252fc 100644 --- a/cashapppay/src/main/res/values-fr-rFR/strings.xml +++ b/cashapppay/src/main/res/values-fr-rFR/strings.xml @@ -8,5 +8,4 @@ Sauvegarder pour mon prochain paiement - Traitement du paiement en cours… diff --git a/cashapppay/src/main/res/values-hr-rHR/strings.xml b/cashapppay/src/main/res/values-hr-rHR/strings.xml index 0c5a8a7e7b..f1a49b69e7 100644 --- a/cashapppay/src/main/res/values-hr-rHR/strings.xml +++ b/cashapppay/src/main/res/values-hr-rHR/strings.xml @@ -8,5 +8,4 @@ Pohrani za moje sljedeće plaćanje - Obrada plaćanja u tijeku… diff --git a/cashapppay/src/main/res/values-hu-rHU/strings.xml b/cashapppay/src/main/res/values-hu-rHU/strings.xml index 37218519e2..db5ad56615 100644 --- a/cashapppay/src/main/res/values-hu-rHU/strings.xml +++ b/cashapppay/src/main/res/values-hu-rHU/strings.xml @@ -8,5 +8,4 @@ Mentés a következő fizetéshez - Fizetés feldolgozása… diff --git a/cashapppay/src/main/res/values-is-rIS/strings.xml b/cashapppay/src/main/res/values-is-rIS/strings.xml index 8c84a651ed..f9beb5794b 100644 --- a/cashapppay/src/main/res/values-is-rIS/strings.xml +++ b/cashapppay/src/main/res/values-is-rIS/strings.xml @@ -8,5 +8,4 @@ Spara fyrir næstu greiðslu - Unnið úr greiðslu… diff --git a/cashapppay/src/main/res/values-it-rIT/strings.xml b/cashapppay/src/main/res/values-it-rIT/strings.xml index cf28df04c9..9563785e4f 100644 --- a/cashapppay/src/main/res/values-it-rIT/strings.xml +++ b/cashapppay/src/main/res/values-it-rIT/strings.xml @@ -8,5 +8,4 @@ Salva per il prossimo pagamento - Elaborazione del pagamento in corso… diff --git a/cashapppay/src/main/res/values-ja-rJP/strings.xml b/cashapppay/src/main/res/values-ja-rJP/strings.xml index 9cdc8febb8..8944ed199d 100644 --- a/cashapppay/src/main/res/values-ja-rJP/strings.xml +++ b/cashapppay/src/main/res/values-ja-rJP/strings.xml @@ -8,5 +8,4 @@ 次回のお支払いのため詳細を保存 - 支払いを処理しています… diff --git a/cashapppay/src/main/res/values-ko-rKR/strings.xml b/cashapppay/src/main/res/values-ko-rKR/strings.xml index 7e21b66846..32aaeb06b9 100644 --- a/cashapppay/src/main/res/values-ko-rKR/strings.xml +++ b/cashapppay/src/main/res/values-ko-rKR/strings.xml @@ -8,5 +8,4 @@ 다음 결제를 위해 이 수단 저장 - 결제 처리 중… diff --git a/cashapppay/src/main/res/values-lt-rLT/strings.xml b/cashapppay/src/main/res/values-lt-rLT/strings.xml index 8ff48b9685..431023b205 100644 --- a/cashapppay/src/main/res/values-lt-rLT/strings.xml +++ b/cashapppay/src/main/res/values-lt-rLT/strings.xml @@ -8,5 +8,4 @@ Išsaugoti kitam mokėjimui - Mokėjimas apdorojamas… diff --git a/cashapppay/src/main/res/values-lv-rLV/strings.xml b/cashapppay/src/main/res/values-lv-rLV/strings.xml index b87ba14304..a78ccce57c 100644 --- a/cashapppay/src/main/res/values-lv-rLV/strings.xml +++ b/cashapppay/src/main/res/values-lv-rLV/strings.xml @@ -8,5 +8,4 @@ Saglabāt manam nākamajam maksājumam - Notiek maksājuma apstrāde… diff --git a/cashapppay/src/main/res/values-nb-rNO/strings.xml b/cashapppay/src/main/res/values-nb-rNO/strings.xml index 0c036a6d1d..c863f1d07e 100644 --- a/cashapppay/src/main/res/values-nb-rNO/strings.xml +++ b/cashapppay/src/main/res/values-nb-rNO/strings.xml @@ -8,5 +8,4 @@ Lagre til min neste betaling - Behandler betaling… diff --git a/cashapppay/src/main/res/values-nl-rNL/strings.xml b/cashapppay/src/main/res/values-nl-rNL/strings.xml index 9371e67494..9cd55a0534 100644 --- a/cashapppay/src/main/res/values-nl-rNL/strings.xml +++ b/cashapppay/src/main/res/values-nl-rNL/strings.xml @@ -8,5 +8,4 @@ Bewaar voor mijn volgende betaling - Betaling wordt verwerkt… diff --git a/cashapppay/src/main/res/values-pl-rPL/strings.xml b/cashapppay/src/main/res/values-pl-rPL/strings.xml index 10614e4c13..a18f52a602 100644 --- a/cashapppay/src/main/res/values-pl-rPL/strings.xml +++ b/cashapppay/src/main/res/values-pl-rPL/strings.xml @@ -8,5 +8,4 @@ Zapisz na potrzeby następnej płatności - Przetwarzanie płatności… diff --git a/cashapppay/src/main/res/values-pt-rBR/strings.xml b/cashapppay/src/main/res/values-pt-rBR/strings.xml index 89cc49afb1..e96e04a449 100644 --- a/cashapppay/src/main/res/values-pt-rBR/strings.xml +++ b/cashapppay/src/main/res/values-pt-rBR/strings.xml @@ -8,5 +8,4 @@ Salvar para meu próximo pagamento - Processando pagamento… diff --git a/cashapppay/src/main/res/values-pt-rPT/strings.xml b/cashapppay/src/main/res/values-pt-rPT/strings.xml index 83e20e6bc5..225f8b4529 100644 --- a/cashapppay/src/main/res/values-pt-rPT/strings.xml +++ b/cashapppay/src/main/res/values-pt-rPT/strings.xml @@ -8,5 +8,4 @@ Guardar para o meu próximo pagamento - A processar pagamento… diff --git a/cashapppay/src/main/res/values-ro-rRO/strings.xml b/cashapppay/src/main/res/values-ro-rRO/strings.xml index e90e4c3008..fb8655eb49 100644 --- a/cashapppay/src/main/res/values-ro-rRO/strings.xml +++ b/cashapppay/src/main/res/values-ro-rRO/strings.xml @@ -8,5 +8,4 @@ Salvează pentru următoarea mea plată - Se prelucrează plata… diff --git a/cashapppay/src/main/res/values-ru-rRU/strings.xml b/cashapppay/src/main/res/values-ru-rRU/strings.xml index d01ef30545..c14a1aeedf 100644 --- a/cashapppay/src/main/res/values-ru-rRU/strings.xml +++ b/cashapppay/src/main/res/values-ru-rRU/strings.xml @@ -8,5 +8,4 @@ Сохранить для следующего платежа - Платеж обрабатывается… diff --git a/cashapppay/src/main/res/values-sk-rSK/strings.xml b/cashapppay/src/main/res/values-sk-rSK/strings.xml index ff3c302d5a..8198dbd57e 100644 --- a/cashapppay/src/main/res/values-sk-rSK/strings.xml +++ b/cashapppay/src/main/res/values-sk-rSK/strings.xml @@ -8,5 +8,4 @@ Uložiť pre moju ďalšiu platbu - Platba sa spracúva. diff --git a/cashapppay/src/main/res/values-sl-rSI/strings.xml b/cashapppay/src/main/res/values-sl-rSI/strings.xml index e799889891..09bde2f79c 100644 --- a/cashapppay/src/main/res/values-sl-rSI/strings.xml +++ b/cashapppay/src/main/res/values-sl-rSI/strings.xml @@ -8,5 +8,4 @@ Shrani za moje naslednje plačilo - Obdelava plačila… diff --git a/cashapppay/src/main/res/values-sv-rSE/strings.xml b/cashapppay/src/main/res/values-sv-rSE/strings.xml index 085bc46c5c..3bf819354a 100644 --- a/cashapppay/src/main/res/values-sv-rSE/strings.xml +++ b/cashapppay/src/main/res/values-sv-rSE/strings.xml @@ -8,5 +8,4 @@ Spara till min nästa betalning - Behandlar betalning… diff --git a/cashapppay/src/main/res/values-zh-rCN/strings.xml b/cashapppay/src/main/res/values-zh-rCN/strings.xml index 012a1a675a..27a63acc09 100644 --- a/cashapppay/src/main/res/values-zh-rCN/strings.xml +++ b/cashapppay/src/main/res/values-zh-rCN/strings.xml @@ -8,5 +8,4 @@ 保存以便下次支付使用 - 正在处理付款… diff --git a/cashapppay/src/main/res/values-zh-rTW/strings.xml b/cashapppay/src/main/res/values-zh-rTW/strings.xml index 62265679dc..438cc5afb8 100644 --- a/cashapppay/src/main/res/values-zh-rTW/strings.xml +++ b/cashapppay/src/main/res/values-zh-rTW/strings.xml @@ -8,5 +8,4 @@ 儲存以供下次付款使用 - 正在處理付款…… diff --git a/cashapppay/src/main/res/values/strings.xml b/cashapppay/src/main/res/values/strings.xml index c3b37e4f2d..1243ed256a 100644 --- a/cashapppay/src/main/res/values/strings.xml +++ b/cashapppay/src/main/res/values/strings.xml @@ -8,5 +8,4 @@ Save for my next payment - Processing payment… diff --git a/twint/src/main/res/values-ar/strings.xml b/twint/src/main/res/values-ar/strings.xml index 3c676a010f..1b041cc74f 100644 --- a/twint/src/main/res/values-ar/strings.xml +++ b/twint/src/main/res/values-ar/strings.xml @@ -8,4 +8,4 @@ حفظ لمدفوعاتي القادمة - \ No newline at end of file + diff --git a/twint/src/main/res/values-bg-rBG/strings.xml b/twint/src/main/res/values-bg-rBG/strings.xml index b33c81e03f..962a51e8fa 100644 --- a/twint/src/main/res/values-bg-rBG/strings.xml +++ b/twint/src/main/res/values-bg-rBG/strings.xml @@ -8,4 +8,4 @@ Запазване за следващото ми плащане - \ No newline at end of file + diff --git a/twint/src/main/res/values-ca-rES/strings.xml b/twint/src/main/res/values-ca-rES/strings.xml index 2eb0bf4599..e3789995b0 100644 --- a/twint/src/main/res/values-ca-rES/strings.xml +++ b/twint/src/main/res/values-ca-rES/strings.xml @@ -8,4 +8,4 @@ Desa\'l per al meu proper pagament - \ No newline at end of file + diff --git a/twint/src/main/res/values-cs-rCZ/strings.xml b/twint/src/main/res/values-cs-rCZ/strings.xml index ea36c1927e..d842afd337 100644 --- a/twint/src/main/res/values-cs-rCZ/strings.xml +++ b/twint/src/main/res/values-cs-rCZ/strings.xml @@ -8,4 +8,4 @@ Uložit pro příští platby - \ No newline at end of file + diff --git a/twint/src/main/res/values-da-rDK/strings.xml b/twint/src/main/res/values-da-rDK/strings.xml index 83eaefc275..6ead94aac2 100644 --- a/twint/src/main/res/values-da-rDK/strings.xml +++ b/twint/src/main/res/values-da-rDK/strings.xml @@ -8,4 +8,4 @@ Gem til min næste betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-de-rDE/strings.xml b/twint/src/main/res/values-de-rDE/strings.xml index d957332637..a1c7f4ddfa 100644 --- a/twint/src/main/res/values-de-rDE/strings.xml +++ b/twint/src/main/res/values-de-rDE/strings.xml @@ -8,4 +8,4 @@ Für zukünftige Zahlvorgänge speichern - \ No newline at end of file + diff --git a/twint/src/main/res/values-el-rGR/strings.xml b/twint/src/main/res/values-el-rGR/strings.xml index f2b61e492a..6631def477 100644 --- a/twint/src/main/res/values-el-rGR/strings.xml +++ b/twint/src/main/res/values-el-rGR/strings.xml @@ -8,4 +8,4 @@ Αποθήκευση για την επόμενη πληρωμή μου - \ No newline at end of file + diff --git a/twint/src/main/res/values-es-rES/strings.xml b/twint/src/main/res/values-es-rES/strings.xml index 480120a6c0..a78f6b4999 100644 --- a/twint/src/main/res/values-es-rES/strings.xml +++ b/twint/src/main/res/values-es-rES/strings.xml @@ -8,4 +8,4 @@ Recordar para mi próximo pago - \ No newline at end of file + diff --git a/twint/src/main/res/values-et-rEE/strings.xml b/twint/src/main/res/values-et-rEE/strings.xml index 219eb4ee28..c3dc72ac75 100644 --- a/twint/src/main/res/values-et-rEE/strings.xml +++ b/twint/src/main/res/values-et-rEE/strings.xml @@ -8,4 +8,4 @@ Salvesta mu järgmise makse jaoks - \ No newline at end of file + diff --git a/twint/src/main/res/values-fi-rFI/strings.xml b/twint/src/main/res/values-fi-rFI/strings.xml index beb439e1af..97c6ca4835 100644 --- a/twint/src/main/res/values-fi-rFI/strings.xml +++ b/twint/src/main/res/values-fi-rFI/strings.xml @@ -8,4 +8,4 @@ Tallenna seuraavaa maksuani varten - \ No newline at end of file + diff --git a/twint/src/main/res/values-fr-rFR/strings.xml b/twint/src/main/res/values-fr-rFR/strings.xml index c636922f89..eeaf6ecee0 100644 --- a/twint/src/main/res/values-fr-rFR/strings.xml +++ b/twint/src/main/res/values-fr-rFR/strings.xml @@ -8,4 +8,4 @@ Sauvegarder pour mon prochain paiement - \ No newline at end of file + diff --git a/twint/src/main/res/values-hr-rHR/strings.xml b/twint/src/main/res/values-hr-rHR/strings.xml index e0a3103fc5..0c667e965b 100644 --- a/twint/src/main/res/values-hr-rHR/strings.xml +++ b/twint/src/main/res/values-hr-rHR/strings.xml @@ -8,4 +8,4 @@ Pohrani za moje sljedeće plaćanje - \ No newline at end of file + diff --git a/twint/src/main/res/values-hu-rHU/strings.xml b/twint/src/main/res/values-hu-rHU/strings.xml index 71aaa91f6e..4ce8ed203e 100644 --- a/twint/src/main/res/values-hu-rHU/strings.xml +++ b/twint/src/main/res/values-hu-rHU/strings.xml @@ -8,4 +8,4 @@ Mentés a következő fizetéshez - \ No newline at end of file + diff --git a/twint/src/main/res/values-is-rIS/strings.xml b/twint/src/main/res/values-is-rIS/strings.xml index 49a032ebef..bb8b6de721 100644 --- a/twint/src/main/res/values-is-rIS/strings.xml +++ b/twint/src/main/res/values-is-rIS/strings.xml @@ -8,4 +8,4 @@ Spara fyrir næstu greiðslu - \ No newline at end of file + diff --git a/twint/src/main/res/values-it-rIT/strings.xml b/twint/src/main/res/values-it-rIT/strings.xml index 1f450a5576..0547e381fd 100644 --- a/twint/src/main/res/values-it-rIT/strings.xml +++ b/twint/src/main/res/values-it-rIT/strings.xml @@ -8,4 +8,4 @@ Salva per il prossimo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-ja-rJP/strings.xml b/twint/src/main/res/values-ja-rJP/strings.xml index f3d599c38d..4661bf0e39 100644 --- a/twint/src/main/res/values-ja-rJP/strings.xml +++ b/twint/src/main/res/values-ja-rJP/strings.xml @@ -8,4 +8,4 @@ 次回のお支払いのため詳細を保存 - \ No newline at end of file + diff --git a/twint/src/main/res/values-ko-rKR/strings.xml b/twint/src/main/res/values-ko-rKR/strings.xml index ef67d17d60..11d6a13add 100644 --- a/twint/src/main/res/values-ko-rKR/strings.xml +++ b/twint/src/main/res/values-ko-rKR/strings.xml @@ -8,4 +8,4 @@ 다음 결제를 위해 이 수단 저장 - \ No newline at end of file + diff --git a/twint/src/main/res/values-lt-rLT/strings.xml b/twint/src/main/res/values-lt-rLT/strings.xml index 5584f3d058..735847f4d8 100644 --- a/twint/src/main/res/values-lt-rLT/strings.xml +++ b/twint/src/main/res/values-lt-rLT/strings.xml @@ -8,4 +8,4 @@ Išsaugoti kitam mokėjimui - \ No newline at end of file + diff --git a/twint/src/main/res/values-lv-rLV/strings.xml b/twint/src/main/res/values-lv-rLV/strings.xml index 02801275ac..e0175f1d77 100644 --- a/twint/src/main/res/values-lv-rLV/strings.xml +++ b/twint/src/main/res/values-lv-rLV/strings.xml @@ -8,4 +8,4 @@ Saglabāt manam nākamajam maksājumam - \ No newline at end of file + diff --git a/twint/src/main/res/values-nb-rNO/strings.xml b/twint/src/main/res/values-nb-rNO/strings.xml index 3be320881d..766f804875 100644 --- a/twint/src/main/res/values-nb-rNO/strings.xml +++ b/twint/src/main/res/values-nb-rNO/strings.xml @@ -8,4 +8,4 @@ Lagre til min neste betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-nl-rNL/strings.xml b/twint/src/main/res/values-nl-rNL/strings.xml index 47497632a2..ff4c9a96f8 100644 --- a/twint/src/main/res/values-nl-rNL/strings.xml +++ b/twint/src/main/res/values-nl-rNL/strings.xml @@ -8,4 +8,4 @@ Bewaar voor mijn volgende betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-pl-rPL/strings.xml b/twint/src/main/res/values-pl-rPL/strings.xml index c04ebef994..0572c31a14 100644 --- a/twint/src/main/res/values-pl-rPL/strings.xml +++ b/twint/src/main/res/values-pl-rPL/strings.xml @@ -8,4 +8,4 @@ Zapisz na potrzeby następnej płatności - \ No newline at end of file + diff --git a/twint/src/main/res/values-pt-rBR/strings.xml b/twint/src/main/res/values-pt-rBR/strings.xml index ed2bbccfe6..a6572eb2cf 100644 --- a/twint/src/main/res/values-pt-rBR/strings.xml +++ b/twint/src/main/res/values-pt-rBR/strings.xml @@ -8,4 +8,4 @@ Salvar para meu próximo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-pt-rPT/strings.xml b/twint/src/main/res/values-pt-rPT/strings.xml index b61951548e..2c50ac483e 100644 --- a/twint/src/main/res/values-pt-rPT/strings.xml +++ b/twint/src/main/res/values-pt-rPT/strings.xml @@ -8,4 +8,4 @@ Guardar para o meu próximo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-ro-rRO/strings.xml b/twint/src/main/res/values-ro-rRO/strings.xml index 612d0dbc24..102b51e538 100644 --- a/twint/src/main/res/values-ro-rRO/strings.xml +++ b/twint/src/main/res/values-ro-rRO/strings.xml @@ -8,4 +8,4 @@ Salvează pentru următoarea mea plată - \ No newline at end of file + diff --git a/twint/src/main/res/values-ru-rRU/strings.xml b/twint/src/main/res/values-ru-rRU/strings.xml index a0d065257e..cb4cefbc3d 100644 --- a/twint/src/main/res/values-ru-rRU/strings.xml +++ b/twint/src/main/res/values-ru-rRU/strings.xml @@ -8,4 +8,4 @@ Сохранить для следующего платежа - \ No newline at end of file + diff --git a/twint/src/main/res/values-sk-rSK/strings.xml b/twint/src/main/res/values-sk-rSK/strings.xml index 99b5522b90..41bfac319d 100644 --- a/twint/src/main/res/values-sk-rSK/strings.xml +++ b/twint/src/main/res/values-sk-rSK/strings.xml @@ -8,4 +8,4 @@ Uložiť pre moju ďalšiu platbu - \ No newline at end of file + diff --git a/twint/src/main/res/values-sl-rSI/strings.xml b/twint/src/main/res/values-sl-rSI/strings.xml index 36550aca6f..28c2174f42 100644 --- a/twint/src/main/res/values-sl-rSI/strings.xml +++ b/twint/src/main/res/values-sl-rSI/strings.xml @@ -8,4 +8,4 @@ Shrani za moje naslednje plačilo - \ No newline at end of file + diff --git a/twint/src/main/res/values-sv-rSE/strings.xml b/twint/src/main/res/values-sv-rSE/strings.xml index efbef0ed6c..6dd5e8ce64 100644 --- a/twint/src/main/res/values-sv-rSE/strings.xml +++ b/twint/src/main/res/values-sv-rSE/strings.xml @@ -8,4 +8,4 @@ Spara till min nästa betalning - \ No newline at end of file + diff --git a/twint/src/main/res/values-zh-rCN/strings.xml b/twint/src/main/res/values-zh-rCN/strings.xml index 2dc4996f5d..b7d8c86746 100644 --- a/twint/src/main/res/values-zh-rCN/strings.xml +++ b/twint/src/main/res/values-zh-rCN/strings.xml @@ -8,4 +8,4 @@ 保存以便下次支付使用 - \ No newline at end of file + diff --git a/twint/src/main/res/values-zh-rTW/strings.xml b/twint/src/main/res/values-zh-rTW/strings.xml index a84f33995d..2961a62b97 100644 --- a/twint/src/main/res/values-zh-rTW/strings.xml +++ b/twint/src/main/res/values-zh-rTW/strings.xml @@ -8,4 +8,4 @@ 儲存以供下次付款使用 - \ No newline at end of file + diff --git a/twint/src/main/res/values/strings.xml b/twint/src/main/res/values/strings.xml index e6745cc0fc..358a131969 100644 --- a/twint/src/main/res/values/strings.xml +++ b/twint/src/main/res/values/strings.xml @@ -8,4 +8,4 @@ Save for my next payment - \ No newline at end of file + diff --git a/ui-core/src/main/res/values-ar/strings.xml b/ui-core/src/main/res/values-ar/strings.xml index 0c600a31fe..27e07bc79c 100644 --- a/ui-core/src/main/res/values-ar/strings.xml +++ b/ui-core/src/main/res/values-ar/strings.xml @@ -55,4 +55,6 @@ أدخل العنوان يدويًا استخدم هذا العنوان العنوان مطلوب + + جارِ معالجة المدفوعات… diff --git a/ui-core/src/main/res/values-bg-rBG/strings.xml b/ui-core/src/main/res/values-bg-rBG/strings.xml index 60b9caff52..cbfe431112 100644 --- a/ui-core/src/main/res/values-bg-rBG/strings.xml +++ b/ui-core/src/main/res/values-bg-rBG/strings.xml @@ -55,4 +55,6 @@ Въведете адреса ръчно Използвайте този адрес Изисква се адрес + + Обработка на плащането… diff --git a/ui-core/src/main/res/values-ca-rES/strings.xml b/ui-core/src/main/res/values-ca-rES/strings.xml index 0dcd892455..bb82408927 100644 --- a/ui-core/src/main/res/values-ca-rES/strings.xml +++ b/ui-core/src/main/res/values-ca-rES/strings.xml @@ -55,4 +55,6 @@ Introduïu l\'adreça manualment Utilitza aquesta adreça Adreça obligatòria + + S\'esta processant el pagament… diff --git a/ui-core/src/main/res/values-cs-rCZ/strings.xml b/ui-core/src/main/res/values-cs-rCZ/strings.xml index 08a6d31a05..bf5cdd623f 100644 --- a/ui-core/src/main/res/values-cs-rCZ/strings.xml +++ b/ui-core/src/main/res/values-cs-rCZ/strings.xml @@ -55,4 +55,6 @@ Zadejte adresu ručně Použijte tuto adresu Požadovaná adresa + + Zpracování platby… diff --git a/ui-core/src/main/res/values-da-rDK/strings.xml b/ui-core/src/main/res/values-da-rDK/strings.xml index 16a357ebe7..6d7800ca30 100644 --- a/ui-core/src/main/res/values-da-rDK/strings.xml +++ b/ui-core/src/main/res/values-da-rDK/strings.xml @@ -55,4 +55,6 @@ Indtast adresse manuelt Brug denne adresse Adresse er påkrævet + + Behandler betaling… diff --git a/ui-core/src/main/res/values-de-rDE/strings.xml b/ui-core/src/main/res/values-de-rDE/strings.xml index c0f7f3fb4a..9375a2d105 100644 --- a/ui-core/src/main/res/values-de-rDE/strings.xml +++ b/ui-core/src/main/res/values-de-rDE/strings.xml @@ -55,4 +55,6 @@ Geben Sie die Adresse manuell ein Diese Adresse verwenden Adresse erforderlich + + Zahlung wird verarbeitet… diff --git a/ui-core/src/main/res/values-el-rGR/strings.xml b/ui-core/src/main/res/values-el-rGR/strings.xml index 9df7e3b258..6b72311c35 100644 --- a/ui-core/src/main/res/values-el-rGR/strings.xml +++ b/ui-core/src/main/res/values-el-rGR/strings.xml @@ -55,4 +55,6 @@ Εισαγάγετε τη διεύθυνση μη αυτόματα Χρησιμοποιήστε αυτήν τη διεύθυνση Απαιτείται διεύθυνση + + Επεξεργασία πληρωμής… diff --git a/ui-core/src/main/res/values-es-rES/strings.xml b/ui-core/src/main/res/values-es-rES/strings.xml index 56db8cd164..f04df842c8 100644 --- a/ui-core/src/main/res/values-es-rES/strings.xml +++ b/ui-core/src/main/res/values-es-rES/strings.xml @@ -55,4 +55,6 @@ Introduzca la dirección manualmente Usar esta dirección Se necesita la dirección + + Procesando pago… diff --git a/ui-core/src/main/res/values-et-rEE/strings.xml b/ui-core/src/main/res/values-et-rEE/strings.xml index be7feabe52..5102dad98c 100644 --- a/ui-core/src/main/res/values-et-rEE/strings.xml +++ b/ui-core/src/main/res/values-et-rEE/strings.xml @@ -55,4 +55,6 @@ Sisestage aadress käsitsi Kasuta seda aadressi Aadress on kohustuslik + + Makse töötlemine … diff --git a/ui-core/src/main/res/values-fi-rFI/strings.xml b/ui-core/src/main/res/values-fi-rFI/strings.xml index 40768d332f..2bed3074a4 100644 --- a/ui-core/src/main/res/values-fi-rFI/strings.xml +++ b/ui-core/src/main/res/values-fi-rFI/strings.xml @@ -55,4 +55,6 @@ Syötä osoite manuaalisesti Käytä tätä osoitetta Osoite vaaditaan + + Maksua käsitellään… diff --git a/ui-core/src/main/res/values-fr-rFR/strings.xml b/ui-core/src/main/res/values-fr-rFR/strings.xml index 7bfef50a70..0e190448d8 100644 --- a/ui-core/src/main/res/values-fr-rFR/strings.xml +++ b/ui-core/src/main/res/values-fr-rFR/strings.xml @@ -55,4 +55,6 @@ Saisissez l\'adresse manuellement Utiliser cette adresse Adresse requise + + Traitement du paiement en cours… diff --git a/ui-core/src/main/res/values-hr-rHR/strings.xml b/ui-core/src/main/res/values-hr-rHR/strings.xml index 8756b3c7f0..d78008307e 100644 --- a/ui-core/src/main/res/values-hr-rHR/strings.xml +++ b/ui-core/src/main/res/values-hr-rHR/strings.xml @@ -55,4 +55,6 @@ Ručno unesite adresu Koristi ovu adresu Potrebna je adresa + + Obrada plaćanja u tijeku… diff --git a/ui-core/src/main/res/values-hu-rHU/strings.xml b/ui-core/src/main/res/values-hu-rHU/strings.xml index 3fe5f079e7..b148728aca 100644 --- a/ui-core/src/main/res/values-hu-rHU/strings.xml +++ b/ui-core/src/main/res/values-hu-rHU/strings.xml @@ -55,4 +55,6 @@ Manuálisan írjon be egy címet Használja ezt a címet A cím megadása kötelező + + Fizetés feldolgozása… diff --git a/ui-core/src/main/res/values-is-rIS/strings.xml b/ui-core/src/main/res/values-is-rIS/strings.xml index 5357859c35..b41822122d 100644 --- a/ui-core/src/main/res/values-is-rIS/strings.xml +++ b/ui-core/src/main/res/values-is-rIS/strings.xml @@ -55,4 +55,6 @@ Slá inn heimilisfang handvirkt Nota þetta heimilisfang Heimilisfang er áskilið + + Unnið úr greiðslu… diff --git a/ui-core/src/main/res/values-it-rIT/strings.xml b/ui-core/src/main/res/values-it-rIT/strings.xml index 362587fe11..bf4ec27201 100644 --- a/ui-core/src/main/res/values-it-rIT/strings.xml +++ b/ui-core/src/main/res/values-it-rIT/strings.xml @@ -55,4 +55,6 @@ Inserisci l\'indirizzo manualmente Usa questo indirizzo Indirizzo richiesto + + Elaborazione del pagamento in corso… diff --git a/ui-core/src/main/res/values-ja-rJP/strings.xml b/ui-core/src/main/res/values-ja-rJP/strings.xml index f4779958bd..ac9b224306 100644 --- a/ui-core/src/main/res/values-ja-rJP/strings.xml +++ b/ui-core/src/main/res/values-ja-rJP/strings.xml @@ -55,4 +55,6 @@ 住所を手動で入力してください この住所を使用する 住所が必要です + + 支払いを処理しています… diff --git a/ui-core/src/main/res/values-ko-rKR/strings.xml b/ui-core/src/main/res/values-ko-rKR/strings.xml index 0ceaad2bd9..41a35e7022 100644 --- a/ui-core/src/main/res/values-ko-rKR/strings.xml +++ b/ui-core/src/main/res/values-ko-rKR/strings.xml @@ -55,4 +55,6 @@ 수동으로 주소 입력 이 주소 사용 주소 필수 + + 결제 처리 중… diff --git a/ui-core/src/main/res/values-lt-rLT/strings.xml b/ui-core/src/main/res/values-lt-rLT/strings.xml index 63643d7a4a..0d61c38d3f 100644 --- a/ui-core/src/main/res/values-lt-rLT/strings.xml +++ b/ui-core/src/main/res/values-lt-rLT/strings.xml @@ -55,4 +55,6 @@ Įveskite adresą rankiniu būdu Naudokite šį adresą Adresas privalomas + + Mokėjimas apdorojamas… diff --git a/ui-core/src/main/res/values-lv-rLV/strings.xml b/ui-core/src/main/res/values-lv-rLV/strings.xml index 75ad230807..de3ae5ef24 100644 --- a/ui-core/src/main/res/values-lv-rLV/strings.xml +++ b/ui-core/src/main/res/values-lv-rLV/strings.xml @@ -55,4 +55,6 @@ Ievadiet adresi manuāli Izmantot šo adresi Nepieciešama adrese + + Notiek maksājuma apstrāde… diff --git a/ui-core/src/main/res/values-nb-rNO/strings.xml b/ui-core/src/main/res/values-nb-rNO/strings.xml index 60b237c954..43b34dd4a1 100644 --- a/ui-core/src/main/res/values-nb-rNO/strings.xml +++ b/ui-core/src/main/res/values-nb-rNO/strings.xml @@ -55,4 +55,6 @@ Skriv inn adressen manuelt Bruk denne adressen Adresse er nødvendig + + Behandler betaling… diff --git a/ui-core/src/main/res/values-nl-rNL/strings.xml b/ui-core/src/main/res/values-nl-rNL/strings.xml index ddab9d7a2a..49d65bba1f 100644 --- a/ui-core/src/main/res/values-nl-rNL/strings.xml +++ b/ui-core/src/main/res/values-nl-rNL/strings.xml @@ -55,4 +55,6 @@ Voer het adres handmatig in Dit adres gebruiken Adres verplicht + + Betaling wordt verwerkt… diff --git a/ui-core/src/main/res/values-pl-rPL/strings.xml b/ui-core/src/main/res/values-pl-rPL/strings.xml index 0b1549741a..f7ba1e3212 100644 --- a/ui-core/src/main/res/values-pl-rPL/strings.xml +++ b/ui-core/src/main/res/values-pl-rPL/strings.xml @@ -55,4 +55,6 @@ Wprowadź adres ręcznie Użyj tego adresu Wymagane jest podanie adresu + + Przetwarzanie płatności… diff --git a/ui-core/src/main/res/values-pt-rBR/strings.xml b/ui-core/src/main/res/values-pt-rBR/strings.xml index f9c6125cd8..7fb23e3234 100644 --- a/ui-core/src/main/res/values-pt-rBR/strings.xml +++ b/ui-core/src/main/res/values-pt-rBR/strings.xml @@ -55,4 +55,6 @@ Inserir endereço manualmente Usar este endereço O endereço é obrigatório + + Processando pagamento… diff --git a/ui-core/src/main/res/values-pt-rPT/strings.xml b/ui-core/src/main/res/values-pt-rPT/strings.xml index 85748bf8ab..e53023132a 100644 --- a/ui-core/src/main/res/values-pt-rPT/strings.xml +++ b/ui-core/src/main/res/values-pt-rPT/strings.xml @@ -55,4 +55,6 @@ Introduza o endereço manualmente Utilize este endereço Endereço necessário + + A processar pagamento… diff --git a/ui-core/src/main/res/values-ro-rRO/strings.xml b/ui-core/src/main/res/values-ro-rRO/strings.xml index d4e2fba271..5622e9aeb0 100644 --- a/ui-core/src/main/res/values-ro-rRO/strings.xml +++ b/ui-core/src/main/res/values-ro-rRO/strings.xml @@ -55,4 +55,6 @@ Introduceți adresa manual Folosiți această adresă Adresa este necesară + + Se prelucrează plata… diff --git a/ui-core/src/main/res/values-ru-rRU/strings.xml b/ui-core/src/main/res/values-ru-rRU/strings.xml index 403d293a36..ddaf975f5b 100644 --- a/ui-core/src/main/res/values-ru-rRU/strings.xml +++ b/ui-core/src/main/res/values-ru-rRU/strings.xml @@ -55,4 +55,6 @@ Ввести адрес вручную Используйте этот адрес Требуется адрес + + Платеж обрабатывается… diff --git a/ui-core/src/main/res/values-sk-rSK/strings.xml b/ui-core/src/main/res/values-sk-rSK/strings.xml index e70c9c52cf..e1907ba92d 100644 --- a/ui-core/src/main/res/values-sk-rSK/strings.xml +++ b/ui-core/src/main/res/values-sk-rSK/strings.xml @@ -55,4 +55,6 @@ Manuálne zadajte adresu Použite túto adresu Adresa sa požaduje + + Platba sa spracúva. diff --git a/ui-core/src/main/res/values-sl-rSI/strings.xml b/ui-core/src/main/res/values-sl-rSI/strings.xml index e9ab25d9f0..313d5c7001 100644 --- a/ui-core/src/main/res/values-sl-rSI/strings.xml +++ b/ui-core/src/main/res/values-sl-rSI/strings.xml @@ -55,4 +55,6 @@ Naslov vnesite ročno Uporabite ta naslov Naslov je obvezen + + Obdelava plačila… diff --git a/ui-core/src/main/res/values-sv-rSE/strings.xml b/ui-core/src/main/res/values-sv-rSE/strings.xml index 549b056e98..a8853b8a33 100644 --- a/ui-core/src/main/res/values-sv-rSE/strings.xml +++ b/ui-core/src/main/res/values-sv-rSE/strings.xml @@ -55,4 +55,6 @@ Ange adress manuellt Använd denna adress Adress krävs + + Behandlar betalning… diff --git a/ui-core/src/main/res/values-zh-rCN/strings.xml b/ui-core/src/main/res/values-zh-rCN/strings.xml index d0aecfc55d..c2dbe72405 100644 --- a/ui-core/src/main/res/values-zh-rCN/strings.xml +++ b/ui-core/src/main/res/values-zh-rCN/strings.xml @@ -55,4 +55,6 @@ 手动输入地址 使用此地址 地址为必填项 + + 正在处理付款… diff --git a/ui-core/src/main/res/values-zh-rTW/strings.xml b/ui-core/src/main/res/values-zh-rTW/strings.xml index ac39b50ea0..02e9e7ab85 100644 --- a/ui-core/src/main/res/values-zh-rTW/strings.xml +++ b/ui-core/src/main/res/values-zh-rTW/strings.xml @@ -55,4 +55,6 @@ 手動輸入地址 使用此地址 必須填寫地址 + + 正在處理付款…… From 7e10d4df3ece7f8fe60fe40aceb09f6a20fbee23 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:19:54 +0200 Subject: [PATCH 44/95] Show loading state in Google Pay COAND-1012 --- .../internal/ui/DefaultGooglePayDelegate.kt | 6 ++++-- .../googlepay/internal/ui/GooglePayViewProvider.kt | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 2814b6b7e9..5e01daab54 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -69,7 +69,7 @@ internal class DefaultGooglePayDelegate( override val submitFlow: Flow = submitHandler.submitFlow - private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) override val viewFlow: Flow = _viewFlow private var _coroutineScope: CoroutineScope? = null @@ -154,6 +154,9 @@ internal class DefaultGooglePayDelegate( override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } + + _viewFlow.tryEmit(PaymentInProgressViewType) + val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { @@ -249,7 +252,6 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } - @Suppress("USELESS_IS_CHECK") override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt index f6255bdb3d..927e380caf 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -16,14 +16,16 @@ import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView -internal object GooglePayViewProvider : ViewProvider { +internal class GooglePayViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } @@ -32,6 +34,7 @@ internal object GooglePayViewProvider : ViewProvider { layoutInflater: LayoutInflater ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(layoutInflater) + PaymentInProgressViewType -> ProcessingPaymentView(layoutInflater.context) else -> throw IllegalArgumentException("Unsupported view type") } } @@ -43,7 +46,12 @@ internal class GooglePayButtonViewProvider : ButtonViewProvider { internal object GooglePayComponentViewType : ButtonComponentViewType { override val buttonViewProvider: ButtonViewProvider get() = GooglePayButtonViewProvider() - override val viewProvider: ViewProvider = GooglePayViewProvider + override val viewProvider: ViewProvider get() = GooglePayViewProvider() override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = GooglePayViewProvider() +} From d63fa2d0951d17cf84372a5232840da0ed1d25c8 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:20:37 +0200 Subject: [PATCH 45/95] Show loading state in Twint COAND-1012 --- .../checkout/twint/internal/ui/DefaultTwintDelegate.kt | 2 ++ .../adyen/checkout/twint/internal/ui/TwintViewProvider.kt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt index 4fb4c70b86..2b8788813d 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt @@ -159,6 +159,8 @@ internal class DefaultTwintDelegate( val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + _viewFlow.tryEmit(PaymentInProgressViewType) + val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt index 15907709d4..8b6f33709d 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt @@ -14,11 +14,13 @@ import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView internal class TwintViewProvider : ViewProvider { override fun getView(viewType: ComponentViewType, context: Context): ComponentView = when (viewType) { TwintComponentViewType -> TwintView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } } @@ -29,3 +31,8 @@ internal object TwintComponentViewType : ButtonComponentViewType { override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = TwintViewProvider() +} From 1a0ab527d43046b39ddc9bd05cb8ae369b1b0961 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:42:52 +0200 Subject: [PATCH 46/95] Show loading state in iDeal COAND-1012 --- .../adyen/checkout/ideal/IdealComponent.kt | 12 ++++--- .../ideal/internal/ui/DefaultIdealDelegate.kt | 4 +++ .../ideal/internal/ui/IdealDelegate.kt | 6 +++- .../ideal/internal/ui/IdealViewProvider.kt | 31 +++++++++++++++++++ .../checkout/ideal/IdealComponentTest.kt | 27 +++++++++++++--- 5 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt b/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt index 7a4d9111d7..0e0714abb6 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt @@ -26,6 +26,7 @@ import com.adyen.checkout.ideal.internal.provider.IdealComponentProvider import com.adyen.checkout.ideal.internal.ui.IdealDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewableComponent +import com.adyen.checkout.ui.core.internal.util.mergeViewFlows import kotlinx.coroutines.flow.Flow /** @@ -42,12 +43,13 @@ class IdealComponent internal constructor( ButtonComponent, ActionHandlingComponent by actionHandlingComponent { - @Suppress("ForbiddenComment") - // FIXME: Using actionHandlingComponent.activeDelegate will crash for QR code actions. This is a workaround for the - // actual issue. - override val delegate: ComponentDelegate get() = genericActionDelegate.delegate + override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + idealDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { idealDelegate.initialize(viewModelScope) diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt index ee91075d9e..f80589cc5a 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt @@ -23,6 +23,7 @@ import com.adyen.checkout.components.core.paymentmethod.IdealPaymentMethod import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.ideal.IdealComponentState +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -43,6 +44,9 @@ internal class DefaultIdealDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + private val _viewFlow = MutableStateFlow(PaymentInProgressViewType) + override val viewFlow: Flow = _viewFlow + init { submitChannel.trySend(componentStateFlow.value) } diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt index 472a8dc5d7..be0754166b 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt @@ -10,8 +10,12 @@ package com.adyen.checkout.ideal.internal.ui import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.ideal.IdealComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface IdealDelegate : PaymentComponentDelegate { +internal interface IdealDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { + val componentStateFlow: Flow } diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt new file mode 100644 index 0000000000..270a55f1a0 --- /dev/null +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 18/10/2024. + */ + +package com.adyen.checkout.ideal.internal.ui + +import android.content.Context +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewProvider +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView + +internal class IdealViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context + ): ComponentView = when (viewType) { + PaymentInProgressViewType -> ProcessingPaymentView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = IdealViewProvider() +} diff --git a/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt b/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt index 9b35778316..b73985bc0c 100644 --- a/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt +++ b/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt @@ -8,6 +8,7 @@ import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.ideal.internal.ui.IdealDelegate +import com.adyen.checkout.ideal.internal.ui.PaymentInProgressViewType import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -15,7 +16,6 @@ import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -40,6 +40,7 @@ internal class IdealComponentTest( @BeforeEach fun before() { + whenever(idealDelegate.viewFlow) doReturn MutableStateFlow(PaymentInProgressViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = IdealComponent( @@ -86,9 +87,25 @@ internal class IdealComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { + fun `when component is initialized then view flow should match ideal delegate view flow`() = runTest { component.viewFlow.test { - assertNull(awaitItem()) + assertEquals(PaymentInProgressViewType, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `when ideal delegate view flow emits a value then component view flow should match that value`() = runTest { + val idealDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(idealDelegate.viewFlow) doReturn idealDelegateViewFlow + component = IdealComponent(idealDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) + + component.viewFlow.test { + assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + + idealDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + expectNoEvents() } } @@ -105,7 +122,9 @@ internal class IdealComponentTest( ) component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(PaymentInProgressViewType, awaitItem()) actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) From 3eeef700b6363e621c437c2b9272e14855076646 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:43:10 +0200 Subject: [PATCH 47/95] Show loading state in instant payments COAND-1012 --- .../instant/InstantPaymentComponent.kt | 12 ++++--- .../ui/DefaultInstantPaymentDelegate.kt | 4 +++ .../internal/ui/InstantPaymentDelegate.kt | 6 +++- .../internal/ui/InstantPaymentViewProvider.kt | 31 ++++++++++++++++++ .../instant/InstantPaymentComponentTest.kt | 32 ++++++++++++++++--- 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt diff --git a/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt b/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt index f64efadebd..01c64d1d1b 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.instant.internal.provider.InstantPaymentComponentProvi import com.adyen.checkout.instant.internal.ui.InstantPaymentDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewableComponent +import com.adyen.checkout.ui.core.internal.util.mergeViewFlows import kotlinx.coroutines.flow.Flow /** @@ -32,12 +33,13 @@ class InstantPaymentComponent internal constructor( ViewableComponent, ActionHandlingComponent by actionHandlingComponent { - @Suppress("ForbiddenComment") - // FIXME: Using actionHandlingComponent.activeDelegate will crash for QR code actions. This is a workaround for the - // actual issue. - override val delegate: ComponentDelegate get() = genericActionDelegate.delegate + override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + instantPaymentDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { instantPaymentDelegate.initialize(viewModelScope) diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt index 3fbea06e94..3c19bfa77c 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt @@ -25,6 +25,7 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.instant.InstantComponentState import com.adyen.checkout.instant.internal.ui.model.InstantComponentParams +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -45,6 +46,9 @@ internal class DefaultInstantPaymentDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + private val _viewFlow = MutableStateFlow(PaymentInProgressViewType) + override val viewFlow: Flow = _viewFlow + init { submitChannel.trySend(componentStateFlow.value) } diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt index d9b8bc4095..25ff922505 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt @@ -10,8 +10,12 @@ package com.adyen.checkout.instant.internal.ui import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.instant.InstantComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface InstantPaymentDelegate : PaymentComponentDelegate { +internal interface InstantPaymentDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { + val componentStateFlow: Flow } diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt new file mode 100644 index 0000000000..dd7f7d653c --- /dev/null +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 18/10/2024. + */ + +package com.adyen.checkout.instant.internal.ui + +import android.content.Context +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewProvider +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView + +internal class InstantPaymentViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context + ): ComponentView = when (viewType) { + PaymentInProgressViewType -> ProcessingPaymentView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = InstantPaymentViewProvider() +} diff --git a/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt b/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt index 0d4bb574a0..db08736bdf 100644 --- a/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt +++ b/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt @@ -16,6 +16,7 @@ import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.instant.internal.ui.InstantPaymentDelegate +import com.adyen.checkout.instant.internal.ui.PaymentInProgressViewType import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -23,7 +24,6 @@ import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -48,6 +48,7 @@ internal class InstantPaymentComponentTest( @BeforeEach fun before() { + whenever(instantPaymentDelegate.viewFlow) doReturn MutableStateFlow(PaymentInProgressViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = InstantPaymentComponent( @@ -94,9 +95,30 @@ internal class InstantPaymentComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { + fun `when component is initialized then view flow should match instant delegate view flow`() = runTest { component.viewFlow.test { - assertNull(awaitItem()) + assertEquals(PaymentInProgressViewType, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `when instant delegate view flow emits a value then component view flow should match that value`() = runTest { + val instantDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(instantPaymentDelegate.viewFlow) doReturn instantDelegateViewFlow + component = InstantPaymentComponent( + instantPaymentDelegate = instantPaymentDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = actionHandlingComponent, + componentEventHandler = componentEventHandler, + ) + + component.viewFlow.test { + assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + + instantDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + expectNoEvents() } } @@ -113,7 +135,9 @@ internal class InstantPaymentComponentTest( ) component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(PaymentInProgressViewType, awaitItem()) actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) From 64145f47b46ea151e4eb1cc2f4df0930178dad06 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 23 Oct 2024 11:53:03 +0200 Subject: [PATCH 48/95] Remove loading indicator of internal GooglePayFragment COAND-1012 --- googlepay/src/main/res/layout/fragment_google_pay.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/googlepay/src/main/res/layout/fragment_google_pay.xml b/googlepay/src/main/res/layout/fragment_google_pay.xml index 1e20bfdcc9..6f03758752 100644 --- a/googlepay/src/main/res/layout/fragment_google_pay.xml +++ b/googlepay/src/main/res/layout/fragment_google_pay.xml @@ -8,10 +8,4 @@ - - - - + android:layout_height="wrap_content" /> From 3a1d0758808f0d27982ea3e0f0b900585c27993f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 23 Oct 2024 12:01:07 +0200 Subject: [PATCH 49/95] Add release note about changed styles and strings COAND-1012 --- RELEASE_NOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fdcfd2a392..79d070fcdd 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,3 +23,9 @@ | | | ## Deprecated +- The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. +| Previous | Now | +|-----------------------------------------------------------|------------------------------------------------------------------| +| `AdyenCheckout.CashAppPay.ProgressBar` | `AdyenCheckout.ProcessingPaymentView.ProgressBar` | +| `AdyenCheckout.CashAppPay.WaitingDescriptionTextView` | `AdyenCheckout.ProcessingPaymentView.WaitingDescriptionTextView` | +| `cash_app_pay_waiting_text` | `checkout_processing_payment` | From f727dd8c114fbbc487374bfe85127d9f113d7fa4 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 09:27:14 +0100 Subject: [PATCH 50/95] Correct deprecation messages COAND-941 --- .../java/com/adyen/checkout/googlepay/GooglePayComponent.kt | 2 +- .../adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 450e0bb7f2..6b65bed094 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -83,7 +83,7 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("submit()")) + @Deprecated("Deprecated in favor of submit()", ReplaceWith("submit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 309a96d08d..f815fe6371 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -35,7 +35,7 @@ internal interface GooglePayDelegate : val payEventFlow: Flow> - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("onSubmit()")) + @Deprecated("Deprecated in favor of onSubmit()", ReplaceWith("onSubmit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) fun handleActivityResult(resultCode: Int, data: Intent?) From a55a35eb44da3dbcc9b4b613a124d1b770efb1e5 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:02:37 +0100 Subject: [PATCH 51/95] Add coroutine scope to PayButton initialization COAND-943 --- .../cashapppay/internal/ui/view/CashAppPayButtonView.kt | 3 ++- .../checkout/googlepay/internal/ui/GooglePayButtonView.kt | 5 ++++- .../java/com/adyen/checkout/ui/core/AdyenComponentView.kt | 2 +- .../checkout/ui/core/internal/ui/view/DefaultPayButton.kt | 3 ++- .../com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt index 723facce10..727a1d8bc5 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt @@ -14,6 +14,7 @@ import android.view.LayoutInflater import com.adyen.checkout.cashapppay.databinding.CashAppPayButtonViewBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import kotlinx.coroutines.CoroutineScope internal class CashAppPayButtonView @JvmOverloads constructor( context: Context, @@ -23,7 +24,7 @@ internal class CashAppPayButtonView @JvmOverloads constructor( private val binding = CashAppPayButtonViewBinding.inflate(LayoutInflater.from(context), this) - override fun initialize(delegate: ButtonDelegate) = Unit + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) = Unit override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 6b99b440b6..a9ee5b5999 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -19,6 +19,9 @@ import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton import com.google.android.gms.wallet.button.ButtonOptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal class GooglePayButtonView @JvmOverloads constructor( context: Context, @@ -49,7 +52,7 @@ internal class GooglePayButtonView @JvmOverloads constructor( typedArray.recycle() } - override fun initialize(delegate: ButtonDelegate) { + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) { check(delegate is GooglePayDelegate) val buttonStyle = delegate.componentParams.googlePayButtonStyling diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt index ac5a442fbd..26007deda0 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt @@ -147,7 +147,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) .buttonViewProvider.getButton(context) - buttonView.initialize(buttonDelegate) + buttonView.initialize(buttonDelegate, coroutineScope) buttonView.setText(viewType, componentParams, localizedContext) buttonView.setOnClickListener { buttonDelegate.onSubmit() diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt index a062e2567e..bf02a0dd19 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt @@ -13,6 +13,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.ui.core.databinding.DefaultPayButtonViewBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import kotlinx.coroutines.CoroutineScope internal class DefaultPayButton @JvmOverloads constructor( context: Context, @@ -22,7 +23,7 @@ internal class DefaultPayButton @JvmOverloads constructor( private val binding = DefaultPayButtonViewBinding.inflate(LayoutInflater.from(context), this) - override fun initialize(delegate: ButtonDelegate) = Unit + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) = Unit override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt index 452c93659c..47aea907bb 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt @@ -13,6 +13,7 @@ import android.util.AttributeSet import android.widget.FrameLayout import androidx.annotation.RestrictTo import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import kotlinx.coroutines.CoroutineScope @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class PayButton( @@ -21,7 +22,7 @@ abstract class PayButton( defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { - abstract fun initialize(delegate: ButtonDelegate) + abstract fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) abstract override fun setEnabled(enabled: Boolean) From 887b4890bfe1387eb6a98acedc7eb31c392fb441 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:04:28 +0100 Subject: [PATCH 52/95] Extract Google Pay availability into new class COAND-943 --- .../provider/GooglePayComponentProvider.kt | 33 ++--------- .../util/GooglePayAvailabilityCheck.kt | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index e6e094a0b0..885d6c9d12 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -31,17 +31,16 @@ import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParam import com.adyen.checkout.components.core.internal.ui.model.DropInOverrideParams import com.adyen.checkout.components.core.internal.util.get import com.adyen.checkout.components.core.internal.util.viewModelFactory -import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.core.internal.data.api.HttpClientFactory import com.adyen.checkout.core.internal.util.LocaleProvider -import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.toCheckoutConfiguration import com.adyen.checkout.sessions.core.CheckoutSession @@ -54,10 +53,7 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory import com.adyen.checkout.ui.core.internal.ui.SubmitHandler -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.wallet.Wallet -import java.lang.ref.WeakReference class GooglePayComponentProvider @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -286,14 +282,6 @@ constructor( checkoutConfiguration: CheckoutConfiguration, callback: ComponentAvailableCallback ) { - if ( - GoogleApiAvailability.getInstance() - .isGooglePlayServicesAvailable(application) != ConnectionResult.SUCCESS - ) { - callback.onAvailabilityResult(false, paymentMethod) - return - } - val callbackWeakReference = WeakReference(callback) val componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()).mapToParams( checkoutConfiguration = checkoutConfiguration, deviceLocale = localeProvider.getLocale(application), @@ -302,20 +290,11 @@ constructor( paymentMethod = paymentMethod, ) - val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) - val readyToPayRequest = GooglePayUtils.createIsReadyToPayRequest(componentParams) - val readyToPayTask = paymentsClient.isReadyToPay(readyToPayRequest) - readyToPayTask.addOnSuccessListener { result -> - callbackWeakReference.get()?.onAvailabilityResult(result == true, paymentMethod) - } - readyToPayTask.addOnCanceledListener { - adyenLog(AdyenLogLevel.ERROR) { "GooglePay readyToPay task is cancelled." } - callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) - } - readyToPayTask.addOnFailureListener { - adyenLog(AdyenLogLevel.ERROR, it) { "GooglePay readyToPay task is failed." } - callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) - } + GooglePayAvailabilityCheck(application).isAvailable( + paymentMethod = paymentMethod, + componentParams = componentParams, + callback = callback, + ) } override fun isAvailable( diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt new file mode 100644 index 0000000000..80461df070 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 30/10/2024. + */ + +package com.adyen.checkout.googlepay.internal.util + +import android.app.Application +import com.adyen.checkout.components.core.ComponentAvailableCallback +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.core.AdyenLogLevel +import com.adyen.checkout.core.internal.util.adyenLog +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.wallet.Wallet +import java.lang.ref.WeakReference + +internal class GooglePayAvailabilityCheck( + private val application: Application, +) { + + fun isAvailable( + paymentMethod: PaymentMethod, + componentParams: GooglePayComponentParams, + callback: ComponentAvailableCallback, + ) { + if (GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(application) != ConnectionResult.SUCCESS + ) { + callback.onAvailabilityResult(false, paymentMethod) + return + } + + val callbackWeakReference = WeakReference(callback) + + val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val readyToPayRequest = GooglePayUtils.createIsReadyToPayRequest(componentParams) + val readyToPayTask = paymentsClient.isReadyToPay(readyToPayRequest) + readyToPayTask.addOnSuccessListener { result -> + callbackWeakReference.get()?.onAvailabilityResult(result == true, paymentMethod) + } + readyToPayTask.addOnCanceledListener { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay readyToPay task is cancelled." } + callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) + } + readyToPayTask.addOnFailureListener { + adyenLog(AdyenLogLevel.ERROR, it) { "GooglePay readyToPay task is failed." } + callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) + } + } +} From 333e354c58c73601c951818609ccb3cd496b4ba4 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:05:44 +0100 Subject: [PATCH 53/95] Check Google Pay availability when creating the component COAND-943 --- .../core/ComponentAvailableCallback.kt | 2 +- .../core/PaymentMethodUnavailableException.kt | 16 ++++++++++++++++ .../googlepay/GooglePayUnavailableException.kt | 18 ++++++++++++++++++ .../provider/GooglePayComponentProvider.kt | 2 ++ .../internal/ui/DefaultGooglePayDelegate.kt | 16 ++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt b/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt index f5f2c00f36..f187228cea 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt @@ -7,6 +7,6 @@ */ package com.adyen.checkout.components.core -interface ComponentAvailableCallback { +fun interface ComponentAvailableCallback { fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) } diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt new file mode 100644 index 0000000000..4b529e03af --- /dev/null +++ b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 30/10/2024. + */ + +package com.adyen.checkout.components.core + +import com.adyen.checkout.core.exception.CheckoutException + +open class PaymentMethodUnavailableException( + message: String, + cause: Throwable? = null +) : CheckoutException(message, cause) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt new file mode 100644 index 0000000000..b78803fe78 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 30/10/2024. + */ + +package com.adyen.checkout.googlepay + +import com.adyen.checkout.components.core.PaymentMethodUnavailableException + +class GooglePayUnavailableException( + cause: Throwable? = null, +) : PaymentMethodUnavailableException( + "Google Pay is not available", + cause, +) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index 885d6c9d12..89d484ec28 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -116,6 +116,7 @@ constructor( componentParams = componentParams, analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = @@ -208,6 +209,7 @@ constructor( componentParams = componentParams, analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 5e01daab54..52b50e3539 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -29,8 +29,10 @@ import com.adyen.checkout.core.internal.data.model.ModelUtils import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -59,6 +61,7 @@ internal class DefaultGooglePayDelegate( override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, private val paymentsClient: PaymentsClient, + private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -91,6 +94,19 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.rendered(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + + checkAvailability() + } + + private fun checkAvailability() { + googlePayAvailabilityCheck.isAvailable( + paymentMethod, + componentParams, + ) { isAvailable, _ -> + if (!isAvailable) { + exceptionChannel.trySend(GooglePayUnavailableException()) + } + } } override fun observe( From b6870466c901a7801d7cd2c989115b9eeb839e0e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:06:11 +0100 Subject: [PATCH 54/95] Hide Google Pay button when Google Pay is unavailable COAND-943 --- .../googlepay/internal/ui/GooglePayButtonView.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index a9ee5b5999..4447fa7454 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -12,8 +12,10 @@ import android.content.Context import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater +import androidx.core.view.isVisible import com.adyen.checkout.googlepay.GooglePayButtonTheme import com.adyen.checkout.googlepay.GooglePayButtonType +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @@ -55,6 +57,8 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) { check(delegate is GooglePayDelegate) + observeDelegate(delegate, coroutineScope) + val buttonStyle = delegate.componentParams.googlePayButtonStyling val buttonType = buttonStyle?.buttonType ?: styledButtonType @@ -82,6 +86,16 @@ internal class GooglePayButtonView @JvmOverloads constructor( ) } + private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { + delegate.exceptionFlow + .onEach { e -> + if (e is GooglePayUnavailableException) { + binding.payButton.isVisible = false + } + } + .launchIn(coroutineScope) + } + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } From 9a2d077f277a78dcdc0071001843b7866f5d9b7c Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:10:21 +0100 Subject: [PATCH 55/95] Convert ResultState into a data class This will allow to have customized states. The drawable will now also use it's original color. COAND-943 --- .../checkout/example/ui/compose/ResultContent.kt | 11 ++++------- .../adyen/checkout/example/ui/compose/ResultState.kt | 11 +++++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt index c3676375b6..1c15cc0408 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt @@ -20,7 +20,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.adyen.checkout.example.ui.theme.ExampleTheme @@ -35,19 +37,14 @@ internal fun ResultContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val tint = when (resultState) { - ResultState.SUCCESS -> ExampleTheme.customColors.success - ResultState.PENDING -> ExampleTheme.customColors.warning - ResultState.FAILURE -> MaterialTheme.colorScheme.error - } Icon( painter = painterResource(id = resultState.drawable), contentDescription = null, - tint = tint, + tint = Color.Unspecified, modifier = Modifier.size(100.dp), ) Spacer(modifier = Modifier.height(ExampleTheme.dimensions.grid_2)) - Text(text = resultState.text, style = MaterialTheme.typography.displaySmall) + Text(text = resultState.text, style = MaterialTheme.typography.displaySmall, textAlign = TextAlign.Center) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt index 2debb57da3..7ba1de3d56 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt @@ -10,11 +10,14 @@ package com.adyen.checkout.example.ui.compose import com.adyen.checkout.example.R -enum class ResultState( +data class ResultState( val drawable: Int, val text: String, ) { - SUCCESS(R.drawable.ic_result_success, "Payment successful!"), - PENDING(R.drawable.ic_result_pending, "Payment pending..."), - FAILURE(R.drawable.ic_result_failure, "Payment failed..."), + + companion object { + val SUCCESS = ResultState(R.drawable.ic_result_success, "Payment successful!") + val PENDING = ResultState(R.drawable.ic_result_pending, "Payment pending...") + val FAILURE = ResultState(R.drawable.ic_result_failure, "Payment failed...") + } } From 96c9508b13492aa3118e613e3aa196fe76e4ead3 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:10:57 +0100 Subject: [PATCH 56/95] Remove manual availability check from example integration COAND-943 --- .../compose/SessionsGooglePayViewModel.kt | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index 5d5df697b4..09a7fcfaa9 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -8,18 +8,16 @@ package com.adyen.checkout.example.ui.googlepay.compose -import android.app.Application import android.content.Intent import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.adyen.checkout.components.core.CheckoutConfiguration -import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.ComponentError -import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.action.Action +import com.adyen.checkout.example.R import com.adyen.checkout.example.data.storage.KeyValueStorage import com.adyen.checkout.example.extensions.IODispatcher import com.adyen.checkout.example.extensions.getLogTag @@ -28,8 +26,8 @@ import com.adyen.checkout.example.service.getSessionRequest import com.adyen.checkout.example.service.getSettingsInstallmentOptionsMode import com.adyen.checkout.example.ui.compose.ResultState import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider -import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.sessions.core.CheckoutSession import com.adyen.checkout.sessions.core.CheckoutSessionProvider import com.adyen.checkout.sessions.core.CheckoutSessionResult @@ -49,13 +47,11 @@ import javax.inject.Inject @HiltViewModel internal class SessionsGooglePayViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, - private val application: Application, private val paymentsRepository: PaymentsRepository, private val keyValueStorage: KeyValueStorage, checkoutConfigurationProvider: CheckoutConfigurationProvider, ) : ViewModel(), - SessionComponentCallback, - ComponentAvailableCallback { + SessionComponentCallback { private val checkoutConfiguration = checkoutConfigurationProvider.checkoutConfig @@ -92,7 +88,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( ) updateEvent { SessionsGooglePayEvents.ComponentData(componentData) } - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + updateState { SessionsGooglePayState.ShowButton } } private suspend fun getSession(paymentMethodType: String): CheckoutSession? { @@ -128,35 +124,19 @@ internal class SessionsGooglePayViewModel @Inject constructor( } } - private fun checkGooglePayAvailability( - paymentMethod: PaymentMethod, - checkoutConfiguration: CheckoutConfiguration, - ) { - GooglePayComponent.PROVIDER.isAvailable( - application = application, - paymentMethod = paymentMethod, - checkoutConfiguration = checkoutConfiguration, - callback = this, - ) - } - - override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { - viewModelScope.launch { - if (isAvailable) { - updateState { SessionsGooglePayState.ShowButton } - } else { - onError() - } - } - } - override fun onAction(action: Action) { updateEvent { SessionsGooglePayEvents.Action(action) } } override fun onError(componentError: ComponentError) { - Log.e(TAG, "Component error occurred") - onError() + val exception = componentError.exception + Log.e(TAG, "Component error occurred", exception) + + if (exception is GooglePayUnavailableException) { + onGooglePayUnavailable() + } else { + onError() + } } override fun onFinished(result: SessionPaymentResult) { @@ -171,6 +151,16 @@ internal class SessionsGooglePayViewModel @Inject constructor( else -> ResultState.FAILURE } + private fun onGooglePayUnavailable() { + updateState { + val result = ResultState( + R.drawable.ic_result_failure, + "Google Pay is not available on this device", + ) + SessionsGooglePayState.FinalResult(result) + } + } + private fun onError() { updateState { SessionsGooglePayState.FinalResult(ResultState.FAILURE) } } From c348fb2310d440e3be66b7eb2f4ef25a7db81cce Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 16:45:50 +0100 Subject: [PATCH 57/95] Use output data to manage internal state Exception flow didn't work as the value was only received by one observer. COAND-943 --- .../internal/ui/DefaultGooglePayDelegate.kt | 51 ++++++++++++--- .../internal/ui/GooglePayButtonView.kt | 9 +-- .../internal/ui/GooglePayDelegate.kt | 3 + .../internal/ui/model/GooglePayOutputData.kt | 16 +++++ .../ui/DefaultGooglePayDelegateTest.kt | 63 +++++++++++++++++-- 5 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 52b50e3539..dede4df39e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -32,6 +32,7 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask @@ -64,6 +65,13 @@ internal class DefaultGooglePayDelegate( private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) : GooglePayDelegate { + private val _outputDataFlow = MutableStateFlow(createOutputData()) + override val outputDataFlow: Flow = _outputDataFlow + private val outputData: GooglePayOutputData get() = _outputDataFlow.value + + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + override val viewFlow: Flow = _viewFlow + private val _componentStateFlow = MutableStateFlow(createComponentState()) override val componentStateFlow: Flow = _componentStateFlow @@ -72,9 +80,6 @@ internal class DefaultGooglePayDelegate( override val submitFlow: Flow = submitHandler.submitFlow - private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) - override val viewFlow: Flow = _viewFlow - private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) @@ -103,6 +108,8 @@ internal class DefaultGooglePayDelegate( paymentMethod, componentParams, ) { isAvailable, _ -> + updateOutputData(isButtonVisible = isAvailable) + if (!isAvailable) { exceptionChannel.trySend(GooglePayUnavailableException()) } @@ -128,14 +135,36 @@ internal class DefaultGooglePayDelegate( observerRepository.removeObservers() } + private fun updateOutputData( + isButtonVisible: Boolean = this.outputData.isButtonVisible, + paymentData: PaymentData? = this.outputData.paymentData, + ) { + val newOutputData = createOutputData(isButtonVisible, paymentData) + _outputDataFlow.tryEmit(newOutputData) + updateComponentState(newOutputData) + } + + private fun createOutputData( + isButtonVisible: Boolean = componentParams.isSubmitButtonVisible, + paymentData: PaymentData? = null, + ): GooglePayOutputData { + return GooglePayOutputData( + isButtonVisible = isButtonVisible, + paymentData = paymentData, + ) + } + @VisibleForTesting - internal fun updateComponentState(paymentData: PaymentData?) { + internal fun updateComponentState(outputData: GooglePayOutputData) { adyenLog(AdyenLogLevel.VERBOSE) { "updateComponentState" } - val componentState = createComponentState(paymentData) + val componentState = createComponentState(outputData) _componentStateFlow.tryEmit(componentState) } - private fun createComponentState(paymentData: PaymentData? = null): GooglePayComponentState { + private fun createComponentState( + outputData: GooglePayOutputData = this.outputData + ): GooglePayComponentState { + val paymentData = outputData.paymentData val isValid = paymentData?.let { GooglePayUtils.findToken(it).isNotEmpty() } ?: false @@ -151,10 +180,16 @@ internal class DefaultGooglePayDelegate( amount = componentParams.amount, ) + val isReady = if (shouldShowSubmitButton()) { + outputData.isButtonVisible + } else { + true + } + return GooglePayComponentState( data = paymentComponentData, isInputValid = isValid, - isReady = true, + isReady = isReady, paymentData = paymentData, ) } @@ -247,7 +282,7 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) - updateComponentState(paymentData) + updateOutputData(paymentData = paymentData) submitHandler.onSubmit(_componentStateFlow.value) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 4447fa7454..e0fc29e983 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -15,7 +15,6 @@ import android.view.LayoutInflater import androidx.core.view.isVisible import com.adyen.checkout.googlepay.GooglePayButtonTheme import com.adyen.checkout.googlepay.GooglePayButtonType -import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @@ -87,11 +86,9 @@ internal class GooglePayButtonView @JvmOverloads constructor( } private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { - delegate.exceptionFlow - .onEach { e -> - if (e is GooglePayUnavailableException) { - binding.payButton.isVisible = false - } + delegate.outputDataFlow + .onEach { + binding.payButton.isVisible = it.isButtonVisible } .launchIn(coroutineScope) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index f815fe6371..f2f3f70a89 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -15,6 +15,7 @@ import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task @@ -29,6 +30,8 @@ internal interface GooglePayDelegate : override val componentParams: GooglePayComponentParams + val outputDataFlow: Flow + val componentStateFlow: Flow val exceptionFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt new file mode 100644 index 0000000000..0d8a1fb7d6 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 30/10/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui.model + +import com.google.android.gms.wallet.PaymentData + +internal data class GooglePayOutputData( + val isButtonVisible: Boolean, + val paymentData: PaymentData?, +) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 628812e064..4ce61d0424 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -12,6 +12,7 @@ import android.app.Activity import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration +import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.Configuration import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentMethod @@ -21,11 +22,15 @@ import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment +import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.extensions.test @@ -56,6 +61,7 @@ import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -66,6 +72,7 @@ import java.util.Locale internal class DefaultGooglePayDelegateTest( @Mock private val submitHandler: SubmitHandler, @Mock private val paymentsClient: PaymentsClient, + @Mock private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) { private lateinit var analyticsManager: TestAnalyticsManager @@ -95,7 +102,7 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when payment data is null, then state is not valid`() = runTest { delegate.componentStateFlow.test { - delegate.updateComponentState(null) + delegate.updateComponentState(createOutputData(paymentData = null)) with(awaitItem()) { assertNull(data.paymentMethod) @@ -115,7 +122,7 @@ internal class DefaultGooglePayDelegateTest( val paymentData = TEST_PAYMENT_DATA - delegate.updateComponentState(paymentData) + delegate.updateComponentState(createOutputData(paymentData = paymentData)) val componentState = awaitItem() @@ -165,7 +172,7 @@ internal class DefaultGooglePayDelegateTest( } delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) } } @@ -240,7 +247,7 @@ internal class DefaultGooglePayDelegateTest( delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) } @@ -249,7 +256,7 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when payment is successful and the data is valid, then submit event is tracked`() { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) val result = ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS) delegate.handlePaymentResult(result) @@ -277,6 +284,37 @@ internal class DefaultGooglePayDelegateTest( } } + @ParameterizedTest + @MethodSource("googlePayAvailableSource") + fun `when checking Google Pay availability, then expect isReady and-or exception`( + isSubmitButtonVisible: Boolean, + isAvailable: Boolean, + expectedIsReady: Boolean, + expectedException: CheckoutException?, + ) = runTest { + whenever(googlePayAvailabilityCheck.isAvailable(any(), any(), any())) doAnswer { invocation -> + (invocation.getArgument(2, ComponentAvailableCallback::class.java)) + .onAvailabilityResult(isAvailable, PaymentMethod()) + } + + val config = createCheckoutConfiguration { + setSubmitButtonVisible(isSubmitButtonVisible) + } + delegate = createGooglePayDelegate(config) + val componentStateFlow = delegate.componentStateFlow.test(testScheduler) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + assertEquals(expectedIsReady, componentStateFlow.latestValue.isReady) + + if (expectedException != null) { + assertEquals(expectedException.message, exceptionFlow.latestValue.message) + } else { + assertTrue(exceptionFlow.values.isEmpty()) + } + } + @ParameterizedTest @MethodSource("paymentResultSource") fun `when handling payment result, then success or error is emitted`( @@ -322,9 +360,15 @@ internal class DefaultGooglePayDelegateTest( .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = googlePayAvailabilityCheck, ) } + private fun createOutputData( + isButtonVisible: Boolean = false, + paymentData: PaymentData? = null, + ) = GooglePayOutputData(isButtonVisible, paymentData) + companion object { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" @@ -350,5 +394,14 @@ internal class DefaultGooglePayDelegateTest( arguments(ApiTaskResult(null, Status.RESULT_INTERRUPTED), false), arguments(ApiTaskResult(null, Status(AutoResolveHelper.RESULT_ERROR)), false), ) + + @JvmStatic + fun googlePayAvailableSource() = listOf( + // isSubmitButtonVisible, isAvailable, expectedIsReady, expectedException + arguments(false, false, true, GooglePayUnavailableException()), + arguments(false, true, true, null), + arguments(true, false, false, GooglePayUnavailableException()), + arguments(true, true, true, null), + ) } } From 484fb4e2b048cbbbf75b65a3a70674af944ded58 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 31 Oct 2024 10:09:51 +0100 Subject: [PATCH 58/95] Dump public api spec COAND-943 --- components-core/api/components-core.api | 5 +++++ googlepay/api/googlepay.api | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/components-core/api/components-core.api b/components-core/api/components-core.api index 64733a2042..fcfed82daf 100644 --- a/components-core/api/components-core.api +++ b/components-core/api/components-core.api @@ -936,6 +936,11 @@ public final class com/adyen/checkout/components/core/PaymentMethodTypes { public final fun getUNSUPPORTED_PAYMENT_METHODS ()Ljava/util/List; } +public class com/adyen/checkout/components/core/PaymentMethodUnavailableException : com/adyen/checkout/core/exception/CheckoutException { + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + public final class com/adyen/checkout/components/core/PaymentMethodsApiResponse : com/adyen/checkout/core/internal/data/model/ModelObject { public static final field CREATOR Landroid/os/Parcelable$Creator; public static final field Companion Lcom/adyen/checkout/components/core/PaymentMethodsApiResponse$Companion; diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index a56954af9c..71125b8620 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -227,6 +227,12 @@ public final class com/adyen/checkout/googlepay/GooglePayConfigurationKt { public static synthetic fun googlePay$default (Lcom/adyen/checkout/components/core/CheckoutConfiguration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/adyen/checkout/components/core/CheckoutConfiguration; } +public final class com/adyen/checkout/googlepay/GooglePayUnavailableException : com/adyen/checkout/components/core/PaymentMethodUnavailableException { + public fun ()V + public fun (Ljava/lang/Throwable;)V + public synthetic fun (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + public final class com/adyen/checkout/googlepay/MerchantInfo : com/adyen/checkout/core/internal/data/model/ModelObject { public static final field CREATOR Landroid/os/Parcelable$Creator; public static final field Companion Lcom/adyen/checkout/googlepay/MerchantInfo$Companion; From d0ef838376b9a2dff45958c62f59bb0ad78fac60 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 5 Nov 2024 16:58:57 +0100 Subject: [PATCH 59/95] Add docs for Google Pay COAND-941 --- docs/payment-methods/GOOGLE_PAY.md | 140 ++++++++++++++++++ .../ui/googlepay/GooglePayViewModel.kt | 35 ++--- .../internal/ui/DefaultGooglePayDelegate.kt | 3 +- 3 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 docs/payment-methods/GOOGLE_PAY.md diff --git a/docs/payment-methods/GOOGLE_PAY.md b/docs/payment-methods/GOOGLE_PAY.md new file mode 100644 index 0000000000..87fb02fe96 --- /dev/null +++ b/docs/payment-methods/GOOGLE_PAY.md @@ -0,0 +1,140 @@ +# Google Pay +On this page, you can find additional configuration and a migration guide for Google Pay. + +## Drop-in +### Sessions +The integration works out of the box for sessions implementation. Check out the integration guide [here](https://docs.adyen.com/online-payments/build-your-integration/sessions-flow/?platform=Android&integration=Drop-in). + +### Advanced +There is no additional configuration required. Follow the [Advanced flow integration guide](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Android&integration=Drop-in). + +## Components +Use the following module and component names: +- To import the module use `googlepay`. + +```groovy +implementation "com.adyen.checkout:googlepay:YOUR_VERSION" +``` + +- To launch and show the Component use `GooglePayComponent`. + +```kotlin +val component = GooglePayComponent.PROVIDER.get( + activity = activity, // or fragment = fragment + checkoutSession = checkoutSession, // Should be passed only for sessions + paymentMethod = paymentMethod, + configuration = checkoutConfiguration, + componentCallback = callback, +) +``` + +### Sessions +Make sure to follow the Android Components integration guide for sessions integration [here](https://docs.adyen.com/online-payments/build-your-integration/sessions-flow?platform=Android&integration=Components). + +### Advanced +Make sure to follow the Android Components integration guide for advanced integration [here](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Android&integration=Components). + +## Optional configurations + +```kotlin +CheckoutConfiguration( + environment = environment, + clientKey = clientKey, + … +) { + googlePay { + setSubmitButtonVisible(true) + setMerchantAccount("YOUR_MERCHANT_ACCOUNT") + setGooglePayEnvironment(WalletConstants.ENVIRONMENT_TEST) + setMerchantInfo(…) + setCountryCode("US") + setAllowedAuthMethods(listOf(AllowedAuthMethods.PAN_ONLY)) + setAllowedCardNetworks(listOf("AMEX", "MASTERCARD")) + setAllowPrepaidCards(false) + setAllowCreditCards(true) + setAssuranceDetailsRequired(false) + setEmailRequired(true) + setExistingPaymentMethodRequired(false) + setShippingAddressRequired(true) + setShippingAddressParameters(…) + setBillingAddressRequired(true) + setBillingAddressParameters(…) + setTotalPriceStatus("FINAL") + setGooglePayButtonStyling(…) + } +} +``` + +| Method | Description | +|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `setSubmitButtonVisible` | Set to `true` to display the Google Pay button in the component. The default value is `false`. | +| `setMerchantAccount` | Sets the merchant account to be put in the payment token from Google to Adyen. | +| `setGooglePayEnvironment` | Sets the environment to be used by Google Pay. | +| `setMerchantInfo` | Sets the information about the merchant requesting the payment. | +| `setCountryCode` | Sets the ISO 3166-1 alpha-2 country code where the transaction is processed. | +| `setAllowedAuthMethods` | Sets the supported authentication methods. | +| `setAllowedCardNetworks` | Sets the allowed card networks. The allowed networks are automatically configured based on your account settings, but you can override them here. | +| `setAllowPrepaidCards` | Set to `true` if you support prepaid cards. | +| `setAllowCreditCards` | Set to `true` if you support credit cards. | +| `setAssuranceDetailsRequired` | Set to `true` if you want to request assurance details. | +| `setEmailRequired` | Set to `true` if an email address is required. | +| `setExistingPaymentMethodRequired` | Set to `true` if an existing payment method is required. | +| `setShippingAddressRequired` | Set to `true` if a shipping address is required. | +| `setShippingAddressParameters` | Allows to configure the shipping address parameters. | +| `setBillingAddressRequired` | Set to `true` if a billing address is required. | +| `setBillingAddressParameters` | Allows to configure the billing address parameters. | +| `setTotalPriceStatus` | Sets the status of the total price used. | +| `setGooglePayButtonStyling` | Allows to configure the styling of the Google Pay button. | + +## Migrating to 5.8.0+ +It is not necessary to migrate, but 5.8.0 introduced a simplified integration for Google Pay. This new integration among others gets rid of the deprecated `onActivityResult` and includes the Google Pay button. Follow the steps below to migrate from previous 5.x.x versions to 5.8.0: + +### 1. Remove deprecated Activity Result code + +Add `AdyenComponentView` to your layout and attach the component to it. +```xml + +``` +```kotlin +// Attach the component to the view +binding.componentView.attach(googlePayComponent, lifecycleOwner) + +// Or if you use Jetpack Compose +AdyenComponent(googlePayComponent) +``` + +Now you no longer need activity result related code, so you can clean it up. For example you can remove: +```kotlin +// This function can be deleted +override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + googlePayComponent.handleActivityResult(resultCode, data) +} +``` + +### 2. Display Google Pay button + +If you want to keep displaying a button yourself, then you have to replace the call to `googlePayComponent.startGooglePayScreen(…)` with `googlePayComponent.submit()`. + +To let the component display the Google Pay button inside the `AdyenComponentView` remove your own button and adjust your configuration: +```kotlin +CheckoutConfiguration( + environment = environment, + clientKey = clientKey, + … +) { + googlePay { + setSubmitButtonVisible(true) + setGooglePayButtonStyling(…) // Optionally style the button + } +} +``` + +The `com.google.pay.button:compose-pay-button` dependency can now also be removed from your `build.gradle`. + +### 3. Google Pay availability check + +The `GooglePayComponent` now does the availability check on initialization and will return a `GooglePayUnavailableException` in `onError`. You no longer need to manually call `GooglePayComponent.PROVIDER.isAvailable(…)`. diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index d19ba0dd1c..5d0e55c1f9 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -79,25 +79,26 @@ internal class GooglePayViewModel @Inject constructor( ), ) - val paymentMethod = paymentMethodResponse - ?.paymentMethods - ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } - - if (paymentMethod == null) { - _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) - return@withContext - } + val paymentMethod = paymentMethodResponse + ?.paymentMethods + ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } + + if (paymentMethod == null) { + @Suppress("RestrictedApi") + _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) + return@withContext + } - _googleComponentDataFlow.emit( - GooglePayComponentData( - paymentMethod, - checkoutConfiguration, - this@GooglePayViewModel, - ), - ) + _googleComponentDataFlow.emit( + GooglePayComponentData( + paymentMethod, + checkoutConfiguration, + this@GooglePayViewModel, + ), + ) - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) - } + checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + } private fun checkGooglePayAvailability( paymentMethod: PaymentMethod, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index dede4df39e..038bc4b67d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -91,6 +91,7 @@ internal class DefaultGooglePayDelegate( submitHandler.initialize(coroutineScope, componentStateFlow) initializeAnalytics(coroutineScope) + checkAvailability() } private fun initializeAnalytics(coroutineScope: CoroutineScope) { @@ -99,8 +100,6 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.rendered(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) - - checkAvailability() } private fun checkAvailability() { From 818b2af607dc074cc229c5b21e4da2caa7110dc2 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 13 Nov 2024 17:24:27 +0100 Subject: [PATCH 60/95] Improve migration guide COAND-944 --- docs/payment-methods/GOOGLE_PAY.md | 4 ++- .../ui/googlepay/GooglePayViewModel.kt | 36 +++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/payment-methods/GOOGLE_PAY.md b/docs/payment-methods/GOOGLE_PAY.md index 87fb02fe96..1fc5b3fdf2 100644 --- a/docs/payment-methods/GOOGLE_PAY.md +++ b/docs/payment-methods/GOOGLE_PAY.md @@ -137,4 +137,6 @@ The `com.google.pay.button:compose-pay-button` dependency can now also be remove ### 3. Google Pay availability check -The `GooglePayComponent` now does the availability check on initialization and will return a `GooglePayUnavailableException` in `onError`. You no longer need to manually call `GooglePayComponent.PROVIDER.isAvailable(…)`. +You no longer need to call `GooglePayComponent.PROVIDER.isAvailable(…)`. + +The`GooglePayComponent` now checks if Google Pay is available when you initialize it. If Google Pay is not available, you get a `GooglePayUnavailableException` in `onError`. diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index 5d0e55c1f9..6f958732b6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -79,26 +79,26 @@ internal class GooglePayViewModel @Inject constructor( ), ) - val paymentMethod = paymentMethodResponse - ?.paymentMethods - ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } - - if (paymentMethod == null) { - @Suppress("RestrictedApi") - _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) - return@withContext - } + val paymentMethod = paymentMethodResponse + ?.paymentMethods + ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } + + if (paymentMethod == null) { + @Suppress("RestrictedApi") + _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) + return@withContext + } - _googleComponentDataFlow.emit( - GooglePayComponentData( - paymentMethod, - checkoutConfiguration, - this@GooglePayViewModel, - ), - ) + _googleComponentDataFlow.emit( + GooglePayComponentData( + paymentMethod, + checkoutConfiguration, + this@GooglePayViewModel, + ), + ) - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) - } + checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + } private fun checkGooglePayAvailability( paymentMethod: PaymentMethod, From 7150ad129929b07411cff787ae2c09384fb0b360 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 20 Nov 2024 14:38:48 +0100 Subject: [PATCH 61/95] Move loading state to GooglePayView Previously, the GooglePayView was replaced with ProcessingPaymentView. This causes the Activity Result API to fail and not deliver the result in case the view got recreated on configuration change. By keeping the GooglePayView in view it will be able to successfully restore it's state and deliver the activity result. Making the loading state part of the GooglePayView is essential to fix this problem. COAND-941 --- .../internal/ui/DefaultGooglePayDelegate.kt | 7 +++++-- .../googlepay/internal/ui/GooglePayView.kt | 16 ++++++++++++++++ .../internal/ui/GooglePayViewProvider.kt | 8 -------- .../internal/ui/model/GooglePayOutputData.kt | 1 + .../src/main/res/layout/view_google_pay.xml | 8 ++++++++ .../internal/ui/DefaultGooglePayDelegateTest.kt | 15 ++++++++++++++- .../internal/ui/view/ProcessingPaymentView.kt | 4 +++- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 038bc4b67d..a8c1166739 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -136,19 +136,22 @@ internal class DefaultGooglePayDelegate( private fun updateOutputData( isButtonVisible: Boolean = this.outputData.isButtonVisible, + isLoading: Boolean = this.outputData.isLoading, paymentData: PaymentData? = this.outputData.paymentData, ) { - val newOutputData = createOutputData(isButtonVisible, paymentData) + val newOutputData = createOutputData(isButtonVisible, isLoading, paymentData) _outputDataFlow.tryEmit(newOutputData) updateComponentState(newOutputData) } private fun createOutputData( isButtonVisible: Boolean = componentParams.isSubmitButtonVisible, + isLoading: Boolean = !isButtonVisible, paymentData: PaymentData? = null, ): GooglePayOutputData { return GooglePayOutputData( isButtonVisible = isButtonVisible, + isLoading = isLoading, paymentData = paymentData, ) } @@ -205,7 +208,7 @@ internal class DefaultGooglePayDelegate( override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } - _viewFlow.tryEmit(PaymentInProgressViewType) + updateOutputData(isButtonVisible = false, isLoading = true) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt index 25256e3a32..11cbcdd193 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -13,10 +13,14 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout +import androidx.core.view.isVisible import com.adyen.checkout.components.core.internal.ui.ComponentDelegate import com.adyen.checkout.googlepay.databinding.ViewGooglePayBinding +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData import com.adyen.checkout.ui.core.internal.ui.ComponentView import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal class GooglePayView @JvmOverloads @@ -41,12 +45,24 @@ internal constructor( require(delegate is GooglePayDelegate) { "Unsupported delegate type" } this.delegate = delegate initializeFragment(delegate) + + observeDelegate(delegate, coroutineScope) } private fun initializeFragment(delegate: GooglePayDelegate) { binding.fragmentContainer.getFragment()?.initialize(delegate) } + private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { + delegate.outputDataFlow + .onEach { outputDataChanged(it) } + .launchIn(coroutineScope) + } + + private fun outputDataChanged(outputData: GooglePayOutputData) { + binding.processingPaymentView.isVisible = outputData.isLoading + } + override fun highlightValidationErrors() = Unit override fun getView(): View = this diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt index 927e380caf..7c50d0b7a9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -16,7 +16,6 @@ import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider import com.adyen.checkout.ui.core.internal.ui.view.PayButton -import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView internal class GooglePayViewProvider : ViewProvider { @@ -25,7 +24,6 @@ internal class GooglePayViewProvider : ViewProvider { context: Context, ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(context) - PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } @@ -34,7 +32,6 @@ internal class GooglePayViewProvider : ViewProvider { layoutInflater: LayoutInflater ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(layoutInflater) - PaymentInProgressViewType -> ProcessingPaymentView(layoutInflater.context) else -> throw IllegalArgumentException("Unsupported view type") } } @@ -50,8 +47,3 @@ internal object GooglePayComponentViewType : ButtonComponentViewType { override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } - -internal object PaymentInProgressViewType : ComponentViewType { - - override val viewProvider: ViewProvider get() = GooglePayViewProvider() -} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt index 0d8a1fb7d6..0b96a3796b 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt @@ -12,5 +12,6 @@ import com.google.android.gms.wallet.PaymentData internal data class GooglePayOutputData( val isButtonVisible: Boolean, + val isLoading: Boolean, val paymentData: PaymentData?, ) diff --git a/googlepay/src/main/res/layout/view_google_pay.xml b/googlepay/src/main/res/layout/view_google_pay.xml index ce06d9bc88..cfcb87f1b4 100644 --- a/googlepay/src/main/res/layout/view_google_pay.xml +++ b/googlepay/src/main/res/layout/view_google_pay.xml @@ -20,4 +20,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + + + diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 4ce61d0424..318b898816 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -160,6 +160,18 @@ internal class DefaultGooglePayDelegateTest( assertEquals(task, payEventFlow.latestValue) } + @Test + fun `when onSubmit is called, then button is hidden and loading state is shown`() = runTest { + val outputDataFlow = delegate.outputDataFlow.test(testScheduler) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.onSubmit() + + val latestOutputData = outputDataFlow.latestValue + assertFalse(latestOutputData.isButtonVisible) + assertTrue(latestOutputData.isLoading) + } + @ParameterizedTest @MethodSource("amountSource") fun `when input data is valid then amount is propagated in component state if set`( @@ -366,8 +378,9 @@ internal class DefaultGooglePayDelegateTest( private fun createOutputData( isButtonVisible: Boolean = false, + isLoading: Boolean = !isButtonVisible, paymentData: PaymentData? = null, - ) = GooglePayOutputData(isButtonVisible, paymentData) + ) = GooglePayOutputData(isButtonVisible, isLoading, paymentData) companion object { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt index 521649ee01..1f40232df1 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt @@ -23,7 +23,9 @@ import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle import kotlinx.coroutines.CoroutineScope @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -class ProcessingPaymentView @JvmOverloads constructor( +class ProcessingPaymentView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, From acdca83528c6d17dfabc4efc70059827c3b36c0d Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 21 Nov 2024 14:17:37 +0100 Subject: [PATCH 62/95] Make readiness depending on the availability check Before it was based on if the button was shown or not, which can be hidden even if the component state is fully valid and ready. COAND-941 --- .../internal/ui/DefaultGooglePayDelegate.kt | 9 ++++---- .../ui/DefaultGooglePayDelegateTest.kt | 22 ++++++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index a8c1166739..eb55e37e5c 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -86,6 +86,8 @@ internal class DefaultGooglePayDelegate( private val payEventChannel: Channel> = bufferedChannel() override val payEventFlow: Flow> = payEventChannel.receiveAsFlow() + private var isAvailable = false + override fun initialize(coroutineScope: CoroutineScope) { _coroutineScope = coroutineScope submitHandler.initialize(coroutineScope, componentStateFlow) @@ -107,6 +109,7 @@ internal class DefaultGooglePayDelegate( paymentMethod, componentParams, ) { isAvailable, _ -> + this.isAvailable = isAvailable updateOutputData(isButtonVisible = isAvailable) if (!isAvailable) { @@ -182,11 +185,7 @@ internal class DefaultGooglePayDelegate( amount = componentParams.amount, ) - val isReady = if (shouldShowSubmitButton()) { - outputData.isButtonVisible - } else { - true - } + val isReady = isAvailable return GooglePayComponentState( data = paymentComponentData, diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 318b898816..4114e187e5 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -91,7 +91,7 @@ internal class DefaultGooglePayDelegateTest( with(awaitItem()) { assertNull(data.paymentMethod) assertFalse(isInputValid) - assertTrue(isReady) + assertFalse(isReady) assertNull(paymentData) } @@ -101,6 +101,9 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when payment data is null, then state is not valid`() = runTest { + withAvailabilityCheck(true) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { delegate.updateComponentState(createOutputData(paymentData = null)) @@ -117,6 +120,9 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when creating component state with valid payment data, then the state is propagated`() = runTest { + withAvailabilityCheck(true) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { skipItems(1) @@ -304,10 +310,7 @@ internal class DefaultGooglePayDelegateTest( expectedIsReady: Boolean, expectedException: CheckoutException?, ) = runTest { - whenever(googlePayAvailabilityCheck.isAvailable(any(), any(), any())) doAnswer { invocation -> - (invocation.getArgument(2, ComponentAvailableCallback::class.java)) - .onAvailabilityResult(isAvailable, PaymentMethod()) - } + withAvailabilityCheck(isAvailable) val config = createCheckoutConfiguration { setSubmitButtonVisible(isSubmitButtonVisible) @@ -382,6 +385,13 @@ internal class DefaultGooglePayDelegateTest( paymentData: PaymentData? = null, ) = GooglePayOutputData(isButtonVisible, isLoading, paymentData) + private fun withAvailabilityCheck(isAvailable: Boolean) { + whenever(googlePayAvailabilityCheck.isAvailable(any(), any(), any())) doAnswer { invocation -> + (invocation.getArgument(2, ComponentAvailableCallback::class.java)) + .onAvailabilityResult(isAvailable, PaymentMethod()) + } + } + companion object { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" @@ -411,7 +421,7 @@ internal class DefaultGooglePayDelegateTest( @JvmStatic fun googlePayAvailableSource() = listOf( // isSubmitButtonVisible, isAvailable, expectedIsReady, expectedException - arguments(false, false, true, GooglePayUnavailableException()), + arguments(false, false, false, GooglePayUnavailableException()), arguments(false, true, true, null), arguments(true, false, false, GooglePayUnavailableException()), arguments(true, true, true, null), From c495887b944172f99d97b95bbc7de4530df94d28 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Mon, 9 Dec 2024 17:23:44 +0100 Subject: [PATCH 63/95] Track error events for Google Pay COAND-1009 --- .../internal/ui/DefaultGooglePayDelegate.kt | 4 ++++ .../ui/DefaultGooglePayDelegateTest.kt | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index eb55e37e5c..093fab3c6d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -231,16 +231,19 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.RESULT_ERROR -> { val statusMessage: String = paymentDataTaskResult.status.statusMessage?.let { ": $it" }.orEmpty() adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an error$statusMessage" } + trackThirdPartyErrorEvent() exceptionChannel.trySend(ComponentException("GooglePay encountered an error$statusMessage")) } CommonStatusCodes.INTERNAL_ERROR -> { adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an internal error" } + trackThirdPartyErrorEvent() exceptionChannel.trySend(ComponentException("GooglePay encountered an internal error")) } else -> { adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an unexpected error, statusCode: $statusCode" } + trackThirdPartyErrorEvent() exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) } } @@ -275,6 +278,7 @@ internal class DefaultGooglePayDelegate( private fun initiatePayment(paymentData: PaymentData?) { if (paymentData == null) { adyenLog(AdyenLogLevel.ERROR) { "Payment data is null" } + trackThirdPartyErrorEvent() exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) return } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 4114e187e5..cf00f955bc 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -290,6 +290,20 @@ internal class DefaultGooglePayDelegateTest( analyticsManager.assertIsCleared() } + @ParameterizedTest + @MethodSource("com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegateTest#paymentResultErrorSource") + fun `when handling payment result is error, then error event is tracked`( + result: ApiTaskResult + ) { + delegate.handlePaymentResult(result) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when activity result is OK and data is null, then error event is tracked`() { delegate.handleActivityResult(Activity.RESULT_OK, data = null) @@ -426,5 +440,14 @@ internal class DefaultGooglePayDelegateTest( arguments(true, false, false, GooglePayUnavailableException()), arguments(true, true, true, null), ) + + @JvmStatic + fun paymentResultErrorSource() = listOf( + // result + arguments(ApiTaskResult(null, Status.RESULT_SUCCESS)), + arguments(ApiTaskResult(null, Status.RESULT_INTERNAL_ERROR)), + arguments(ApiTaskResult(null, Status.RESULT_INTERRUPTED)), + arguments(ApiTaskResult(null, Status(AutoResolveHelper.RESULT_ERROR))), + ) } } From c3d8c6e19f2d8aa4cee2ace43e8e5d853466670a Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 11 Dec 2024 10:49:27 +0100 Subject: [PATCH 64/95] Fix formatting of release notes COAND-1009 --- RELEASE_NOTES.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 79d070fcdd..cabb84c1fe 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -24,8 +24,8 @@ ## Deprecated - The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. -| Previous | Now | -|-----------------------------------------------------------|------------------------------------------------------------------| -| `AdyenCheckout.CashAppPay.ProgressBar` | `AdyenCheckout.ProcessingPaymentView.ProgressBar` | -| `AdyenCheckout.CashAppPay.WaitingDescriptionTextView` | `AdyenCheckout.ProcessingPaymentView.WaitingDescriptionTextView` | -| `cash_app_pay_waiting_text` | `checkout_processing_payment` | + | Previous | Now | + |-----------------------------------------------------------|------------------------------------------------------------------| + | `AdyenCheckout.CashAppPay.ProgressBar` | `AdyenCheckout.ProcessingPaymentView.ProgressBar` | + | `AdyenCheckout.CashAppPay.WaitingDescriptionTextView` | `AdyenCheckout.ProcessingPaymentView.WaitingDescriptionTextView` | + | `cash_app_pay_waiting_text` | `checkout_processing_payment` | From c73b4de0e59e923ca0cb4624f6bda705146eb25f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 11 Dec 2024 13:41:18 +0100 Subject: [PATCH 65/95] Always start without loading state COAND-1009 --- .../checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 093fab3c6d..a76bb59db6 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -149,7 +149,7 @@ internal class DefaultGooglePayDelegate( private fun createOutputData( isButtonVisible: Boolean = componentParams.isSubmitButtonVisible, - isLoading: Boolean = !isButtonVisible, + isLoading: Boolean = false, paymentData: PaymentData? = null, ): GooglePayOutputData { return GooglePayOutputData( From b19bed9ad6ee3167811e20d46ca4ec9e3c71a331 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 11 Dec 2024 16:27:42 +0100 Subject: [PATCH 66/95] Add message field for Twint error events COAND-1047 --- .../twint/action/internal/ui/DefaultTwintActionDelegate.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegate.kt b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegate.kt index a23a0fe3e5..80ae486de5 100644 --- a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegate.kt +++ b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegate.kt @@ -164,12 +164,12 @@ internal class DefaultTwintActionDelegate( } TwintPayResult.TW_B_ERROR -> { - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Twint result is error") onError(ComponentException("Twint encountered an error.")) } TwintPayResult.TW_B_APP_NOT_INSTALLED -> { - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Twint app not installed") onError(ComponentException("Twint app not installed.")) } } @@ -223,10 +223,11 @@ internal class DefaultTwintActionDelegate( ) } - private fun trackThirdPartyErrorEvent() { + private fun trackThirdPartyErrorEvent(message: String) { val event = GenericEvents.error( component = action?.paymentMethodType.orEmpty(), event = ErrorEvent.THIRD_PARTY, + message = message ) analyticsManager?.trackEvent(event) } From 6845eb4fb9f620166be02a4f9cfa990e656d1fe8 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 11 Dec 2024 16:27:49 +0100 Subject: [PATCH 67/95] Add message field for Google pay error events COAND-1047 --- .../internal/ui/DefaultGooglePayDelegate.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index a76bb59db6..767da5c4a2 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -231,19 +231,19 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.RESULT_ERROR -> { val statusMessage: String = paymentDataTaskResult.status.statusMessage?.let { ": $it" }.orEmpty() adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an error$statusMessage" } - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Result is error") exceptionChannel.trySend(ComponentException("GooglePay encountered an error$statusMessage")) } CommonStatusCodes.INTERNAL_ERROR -> { adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an internal error" } - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Result is internal error") exceptionChannel.trySend(ComponentException("GooglePay encountered an internal error")) } else -> { adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an unexpected error, statusCode: $statusCode" } - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Unexpected error") exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) } } @@ -254,7 +254,7 @@ internal class DefaultGooglePayDelegate( when (resultCode) { Activity.RESULT_OK -> { if (data == null) { - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Activity result is ok, but data is missing") exceptionChannel.trySend(ComponentException("Result data is null")) return } @@ -266,7 +266,7 @@ internal class DefaultGooglePayDelegate( } AutoResolveHelper.RESULT_ERROR -> { - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Activity result is error") val status = AutoResolveHelper.getStatusFromIntent(data) val statusMessage: String = status?.let { ": ${it.statusMessage}" }.orEmpty() @@ -278,7 +278,7 @@ internal class DefaultGooglePayDelegate( private fun initiatePayment(paymentData: PaymentData?) { if (paymentData == null) { adyenLog(AdyenLogLevel.ERROR) { "Payment data is null" } - trackThirdPartyErrorEvent() + trackThirdPartyErrorEvent("Result is success, but data is missing") exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) return } @@ -291,10 +291,11 @@ internal class DefaultGooglePayDelegate( submitHandler.onSubmit(_componentStateFlow.value) } - private fun trackThirdPartyErrorEvent() { + private fun trackThirdPartyErrorEvent(message: String) { val event = GenericEvents.error( component = getPaymentMethodType(), event = ErrorEvent.THIRD_PARTY, + message = message, ) analyticsManager.trackEvent(event) } From 8760c5902c0ec68485762724ab23c1c50eb683ee Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 27 Nov 2024 11:00:28 +0100 Subject: [PATCH 68/95] Track submit for Cash app pay before onSubmit call COAND-832 --- .../cashapppay/internal/ui/DefaultCashAppPayDelegate.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt index ef655a1c0e..802487ef68 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt @@ -199,9 +199,6 @@ constructor( } private fun initiatePayment() { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - val actions = listOfNotNull( getOneTimeAction(), getOnFileAction(outputData), @@ -276,6 +273,10 @@ constructor( updateInputData { authorizationData = createAuthorizationData(newState.responseData) } + + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + submitHandler.onSubmit(_componentStateFlow.value) } From 976f90f3dade0c949530f0b7377aa356649d789a Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 27 Nov 2024 13:52:16 +0100 Subject: [PATCH 69/95] Track submit event when a value is emitted in the stored components COAND-832 --- .../blik/internal/ui/StoredBlikDelegate.kt | 7 ++- .../internal/ui/StoredBlikDelegateTest.kt | 19 +++++--- .../card/internal/ui/StoredCardDelegate.kt | 6 ++- .../internal/ui/StoredCardDelegateTest.kt | 43 +++++++++++-------- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegate.kt b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegate.kt index 1b76cadc1a..27ebcec00a 100644 --- a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegate.kt +++ b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegate.kt @@ -32,6 +32,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class StoredBlikDelegate( @@ -55,7 +56,7 @@ internal class StoredBlikDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(BlikComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow @@ -104,10 +105,12 @@ internal class StoredBlikDelegate( adyenLog(AdyenLogLevel.ERROR) { "updateInputData should not be called in StoredBlikDelegate" } } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/blik/src/test/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegateTest.kt b/blik/src/test/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegateTest.kt index 13c5a7822f..d0dacb5073 100644 --- a/blik/src/test/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegateTest.kt +++ b/blik/src/test/java/com/adyen/checkout/blik/internal/ui/StoredBlikDelegateTest.kt @@ -27,6 +27,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertFalse @@ -37,7 +39,10 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -130,11 +135,15 @@ class StoredBlikDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createStoredBlikDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index b8d89601de..3e2f07677b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -122,7 +122,7 @@ internal class StoredCardDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(null) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -316,10 +316,12 @@ internal class StoredCardDelegate( ) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt index 46f13ffb7f..e1b32b3719 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt @@ -57,6 +57,8 @@ import com.adyen.checkout.ui.core.internal.util.AddressValidationUtils import com.adyen.threeds2.ThreeDS2Service import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -75,6 +77,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Locale @@ -266,24 +269,26 @@ internal class StoredCardDelegateTest( } @Test - fun `encryption fails, then component state should be invalid and analytics error event is tracked `() = runTest { - cardEncryptor.shouldThrowException = true + fun `encryption fails, then component state should be invalid and analytics error event is tracked `() = + runTest { + cardEncryptor.shouldThrowException = true - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState(createOutputData()) + delegate.componentStateFlow.test { + delegate.updateComponentState(createOutputData()) - val componentState = expectMostRecentItem() + val componentState = expectMostRecentItem() - val expectedEvent = GenericEvents.error(CardPaymentMethod.PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) - analyticsManager.assertLastEventEquals(expectedEvent) + val expectedEvent = + GenericEvents.error(CardPaymentMethod.PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) + analyticsManager.assertLastEventEquals(expectedEvent) - assertTrue(componentState.isReady) - assertFalse(componentState.isInputValid) - assertNull(componentState.lastFourDigits) + assertTrue(componentState.isReady) + assertFalse(componentState.isInputValid) + assertNull(componentState.lastFourDigits) + } } - } @Test fun `security code in output data is invalid, then component state should be invalid`() = runTest { @@ -454,11 +459,15 @@ internal class StoredCardDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createCardDelegate() - val expectedEvent = GenericEvents.submit(PaymentMethodTypes.SCHEME) - analyticsManager.assertLastEventEquals(expectedEvent) + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(PaymentMethodTypes.SCHEME) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test @@ -515,7 +524,7 @@ internal class StoredCardDelegateTest( submitHandler = submitHandler, order = order, cardConfigDataGenerator = cardConfigDataGenerator, - cardValidationMapper = CardValidationMapper() + cardValidationMapper = CardValidationMapper(), ) } From a0074bb99c7d6aa517f5c980192c456cc525d76f Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 27 Nov 2024 13:20:14 +0100 Subject: [PATCH 70/95] Track submit event when a value is emitted in the submitFlow for components COAND-832 --- .../ui/DefaultACHDirectDebitDelegate.kt | 6 +- .../ui/DefaultACHDirectDebitDelegateTest.kt | 62 +++++++++++-------- .../ui/DefaultBacsDirectDebitDelegate.kt | 15 ++--- .../DefaultBacsDirectDebitDelegateTest.kt | 29 ++++----- .../blik/internal/ui/DefaultBlikDelegate.kt | 7 ++- .../internal/ui/DefaultBlikDelegateTest.kt | 19 ++++-- .../internal/ui/DefaultBoletoDelegate.kt | 6 +- .../internal/ui/DefaultBoletoDelegateTest.kt | 19 ++++-- .../card/internal/ui/DefaultCardDelegate.kt | 6 +- .../internal/ui/DefaultCardDelegateTest.kt | 40 +++++++----- .../internal/ui/DefaultCashAppPayDelegate.kt | 11 ++-- .../ui/DefaultCashAppPayDelegateTest.kt | 30 ++++----- .../internal/ui/DefaultEContextDelegate.kt | 7 ++- .../ui/DefaultEContextDelegateTest.kt | 19 ++++-- .../internal/ui/DefaultGiftCardDelegate.kt | 7 ++- .../ui/DefaultGiftCardDelegateTest.kt | 44 ++++++++----- .../internal/ui/DefaultIssuerListDelegate.kt | 7 ++- .../ui/DefaultIssuerListDelegateTest.kt | 19 ++++-- .../mbway/internal/ui/DefaultMBWayDelegate.kt | 11 ++-- .../internal/ui/DefaultMBWayDelegateTest.kt | 19 ++++-- .../ui/DefaultOnlineBankingDelegate.kt | 7 ++- .../ui/DefaultOnlineBankingDelegateTest.kt | 18 ++++-- .../internal/ui/DefaultPayByBankDelegate.kt | 7 ++- .../ui/DefaultPayByBankDelegateTest.kt | 19 ++++-- .../sepa/internal/ui/DefaultSepaDelegate.kt | 7 ++- .../internal/ui/DefaultSepaDelegateTest.kt | 19 ++++-- .../twint/internal/ui/DefaultTwintDelegate.kt | 11 ++-- .../internal/ui/DefaultTwintDelegateTest.kt | 33 +++++----- .../upi/internal/ui/DefaultUPIDelegate.kt | 7 ++- .../upi/internal/ui/DefaultUPIDelegateTest.kt | 19 ++++-- 30 files changed, 331 insertions(+), 199 deletions(-) diff --git a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt index 66cd77a89c..624698b320 100644 --- a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt +++ b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt @@ -104,7 +104,7 @@ internal class DefaultACHDirectDebitDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(ACHDirectDebitComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -363,10 +363,12 @@ internal class DefaultACHDirectDebitDelegate( analyticsManager.clear(this) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit( state = state, diff --git a/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt b/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt index 01eafac788..7f10877a75 100644 --- a/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt +++ b/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt @@ -47,7 +47,9 @@ import com.adyen.checkout.ui.core.internal.util.AddressFormUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -64,7 +66,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -335,33 +340,34 @@ internal class DefaultACHDirectDebitDelegateTest( } @Test - fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = runTest { - genericEncryptor.shouldThrowException = true + fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = + runTest { + genericEncryptor.shouldThrowException = true - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateInputData { - bankLocationId = TEST_BANK_BANK_LOCATION_ID - bankAccountNumber = TEST_BANK_ACCOUNT_NUMBER - ownerName = TEST_OWNER_NAME - address = AddressInputModel( - postalCode = "34220", - street = "Street Name", - stateOrProvince = "province", - houseNumberOrName = "44", - apartmentSuite = "aparment", - city = "Istanbul", - country = "Turkey", - ) - } + delegate.updateInputData { + bankLocationId = TEST_BANK_BANK_LOCATION_ID + bankAccountNumber = TEST_BANK_ACCOUNT_NUMBER + ownerName = TEST_OWNER_NAME + address = AddressInputModel( + postalCode = "34220", + street = "Street Name", + stateOrProvince = "province", + houseNumberOrName = "44", + apartmentSuite = "aparment", + city = "Istanbul", + country = "Turkey", + ) + } - val componentState = delegate.componentStateFlow.first() + val componentState = delegate.componentStateFlow.first() - val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) - analyticsManager.assertLastEventEquals(expectedEvent) + val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) + analyticsManager.assertLastEventEquals(expectedEvent) - assertFalse(componentState.isValid) - } + assertFalse(componentState.isValid) + } @Test fun `when bankLocationId is invalid, then component state should be invalid`() = runTest { @@ -655,11 +661,15 @@ internal class DefaultACHDirectDebitDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createAchDelegate() - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/DefaultBacsDirectDebitDelegate.kt b/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/DefaultBacsDirectDebitDelegate.kt index dbadaf3077..e3f02c5650 100644 --- a/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/DefaultBacsDirectDebitDelegate.kt +++ b/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/DefaultBacsDirectDebitDelegate.kt @@ -34,6 +34,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultBacsDirectDebitDelegate( @@ -55,7 +56,7 @@ internal class DefaultBacsDirectDebitDelegate( private val _componentStateFlow = MutableStateFlow(createComponentState()) override val componentStateFlow: Flow = _componentStateFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -122,6 +123,11 @@ internal class DefaultBacsDirectDebitDelegate( onInputDataChanged() } + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value when (inputData.mode) { @@ -134,12 +140,7 @@ internal class DefaultBacsDirectDebitDelegate( } } - BacsDirectDebitMode.CONFIRMATION -> { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - - submitHandler.onSubmit(state) - } + BacsDirectDebitMode.CONFIRMATION -> submitHandler.onSubmit(state) } } diff --git a/bacs/src/test/java/com/adyen/checkout/bacs/internal/DefaultBacsDirectDebitDelegateTest.kt b/bacs/src/test/java/com/adyen/checkout/bacs/internal/DefaultBacsDirectDebitDelegateTest.kt index 40d6987840..7cc4193646 100644 --- a/bacs/src/test/java/com/adyen/checkout/bacs/internal/DefaultBacsDirectDebitDelegateTest.kt +++ b/bacs/src/test/java/com/adyen/checkout/bacs/internal/DefaultBacsDirectDebitDelegateTest.kt @@ -34,6 +34,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -49,7 +51,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -566,23 +571,15 @@ internal class DefaultBacsDirectDebitDelegateTest( } @Test - fun `when onSubmit is called and component is in confirmation mode, then submit event is tracked`() { - delegate.updateInputData { mode = BacsDirectDebitMode.CONFIRMATION } - - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) - } + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createBacsDelegate() - @Test - fun `when onSubmit is called and component is not in confirmation mode, then submit event is not tracked`() { - delegate.updateInputData { mode = BacsDirectDebitMode.INPUT } - - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventNotEquals(expectedEvent) + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegate.kt b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegate.kt index 910ebf01c0..1979299447 100644 --- a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegate.kt +++ b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegate.kt @@ -33,6 +33,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultBlikDelegate( @@ -58,7 +59,7 @@ internal class DefaultBlikDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(BlikComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow @@ -150,10 +151,12 @@ internal class DefaultBlikDelegate( ) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/blik/src/test/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegateTest.kt b/blik/src/test/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegateTest.kt index 5eb99626cc..bfbe49de84 100644 --- a/blik/src/test/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegateTest.kt +++ b/blik/src/test/java/com/adyen/checkout/blik/internal/ui/DefaultBlikDelegateTest.kt @@ -28,6 +28,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -43,7 +45,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -264,11 +269,15 @@ internal class DefaultBlikDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createBlikDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt index 2b596506f3..f3fa71cfda 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt @@ -83,7 +83,7 @@ internal class DefaultBoletoDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(BoletoComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -292,10 +292,12 @@ internal class DefaultBoletoDelegate( observerRepository.removeObservers() } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { submitHandler.onSubmit(_componentStateFlow.value) } diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt index 5cc9b9f6d3..d34b4b17a8 100644 --- a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt @@ -32,6 +32,8 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -47,7 +49,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -526,11 +531,15 @@ internal class DefaultBoletoDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createBoletoDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 4476ff257a..6cd066770d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -143,7 +143,7 @@ class DefaultCardDelegate( MutableStateFlow(CardComponentViewType.DefaultCardView) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -486,10 +486,12 @@ class DefaultCardDelegate( ) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index 6c08efc7ac..289ea5d5fa 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -76,6 +76,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -95,6 +97,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Locale @@ -695,24 +698,25 @@ internal class DefaultCardDelegateTest( } @Test - fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = runTest { - cardEncryptor.shouldThrowException = true + fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = + runTest { + cardEncryptor.shouldThrowException = true - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState(createOutputData()) + delegate.componentStateFlow.test { + delegate.updateComponentState(createOutputData()) - val componentState = expectMostRecentItem() + val componentState = expectMostRecentItem() - val expectedEvent = GenericEvents.error(PaymentMethodTypes.SCHEME, ErrorEvent.ENCRYPTION) - analyticsManager.assertLastEventEquals(expectedEvent) + val expectedEvent = GenericEvents.error(PaymentMethodTypes.SCHEME, ErrorEvent.ENCRYPTION) + analyticsManager.assertLastEventEquals(expectedEvent) - assertTrue(componentState.isReady) - assertFalse(componentState.isInputValid) - assertNull(componentState.lastFourDigits) + assertTrue(componentState.isReady) + assertFalse(componentState.isInputValid) + assertNull(componentState.lastFourDigits) + } } - } @Test fun `card number in output data is invalid, then component state should be invalid`() = runTest { @@ -1063,11 +1067,15 @@ internal class DefaultCardDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createCardDelegate() - val expectedEvent = GenericEvents.submit(PaymentMethodTypes.SCHEME) - analyticsManager.assertLastEventEquals(expectedEvent) + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(PaymentMethodTypes.SCHEME) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt index 802487ef68..2dee36b608 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegate.kt @@ -52,6 +52,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @@ -82,7 +83,7 @@ constructor( private val exceptionChannel: Channel = bufferedChannel() val exceptionFlow: Flow = exceptionChannel.receiveAsFlow() - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) @@ -192,6 +193,11 @@ constructor( ) } + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + } + override fun onSubmit() { if (isConfirmationRequired()) { initiatePayment() @@ -274,9 +280,6 @@ constructor( authorizationData = createAuthorizationData(newState.responseData) } - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - submitHandler.onSubmit(_componentStateFlow.value) } diff --git a/cashapppay/src/test/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegateTest.kt b/cashapppay/src/test/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegateTest.kt index 43296b23f5..4ecfd30ade 100644 --- a/cashapppay/src/test/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegateTest.kt +++ b/cashapppay/src/test/java/com/adyen/checkout/cashapppay/internal/ui/DefaultCashAppPayDelegateTest.kt @@ -47,6 +47,8 @@ import com.adyen.checkout.test.extensions.test import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -480,20 +482,6 @@ internal class DefaultCashAppPayDelegateTest( analyticsManager.assertLastEventNotEquals(expectedEvent) } - @Test - fun `when delegate is initialized and confirmation is not required, then submit event is tracked`() { - delegate = createDefaultCashAppPayDelegate( - createCheckoutConfiguration(Amount("USD", 10L)) { - setShowStorePaymentField(false) - }, - ) - - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) - } - @Test fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest { analyticsManager.setCheckoutAttemptId(TEST_CHECKOUT_ATTEMPT_ID) @@ -512,11 +500,15 @@ internal class DefaultCashAppPayDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createDefaultCashAppPayDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegate.kt b/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegate.kt index 55ff652f7f..cd94ec5b65 100644 --- a/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegate.kt +++ b/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegate.kt @@ -39,6 +39,7 @@ import com.adyen.checkout.ui.core.internal.util.CountryUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach import java.util.Locale @Suppress("TooManyFunctions", "LongParameterList") @@ -73,7 +74,7 @@ internal class DefaultEContextDelegate< private val _viewFlow: MutableStateFlow = MutableStateFlow(EContextComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -202,10 +203,12 @@ internal class DefaultEContextDelegate< analyticsManager.clear(this) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/econtext/src/test/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegateTest.kt b/econtext/src/test/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegateTest.kt index ce2542bc71..80732f95e0 100644 --- a/econtext/src/test/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegateTest.kt +++ b/econtext/src/test/java/com/adyen/checkout/econtext/internal/ui/DefaultEContextDelegateTest.kt @@ -30,6 +30,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -46,6 +48,9 @@ import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -269,11 +274,15 @@ internal class DefaultEContextDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createEContextDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt index 313fa52e35..5d985c1716 100644 --- a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt +++ b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @@ -90,7 +91,7 @@ class DefaultGiftCardDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(protocol.getComponentViewType()) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow @@ -251,10 +252,12 @@ class DefaultGiftCardDelegate( ) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt b/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt index 66da89942a..27d85525e5 100644 --- a/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt +++ b/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt @@ -40,6 +40,8 @@ import com.adyen.checkout.test.extensions.test import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -56,7 +58,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -127,25 +132,26 @@ internal class DefaultGiftCardDelegateTest( } @Test - fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = runTest { - cardEncryptor.shouldThrowException = true + fun `encryption fails, then component state should be invalid and analytics error event is tracked`() = + runTest { + cardEncryptor.shouldThrowException = true - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState(createValidOutputData()) + delegate.componentStateFlow.test { + delegate.updateComponentState(createValidOutputData()) - val componentState = expectMostRecentItem() + val componentState = expectMostRecentItem() - val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) - analyticsManager.assertLastEventEquals(expectedEvent) + val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.ENCRYPTION) + analyticsManager.assertLastEventEquals(expectedEvent) - assertTrue(componentState.isReady) - assertFalse(componentState.isInputValid) - assertEquals(null, componentState.lastFourDigits) - assertEquals(GiftCardAction.Idle, componentState.giftCardAction) + assertTrue(componentState.isReady) + assertFalse(componentState.isInputValid) + assertEquals(null, componentState.lastFourDigits) + assertEquals(GiftCardAction.Idle, componentState.giftCardAction) + } } - } @Test fun `everything is valid, then component state should be good`() = runTest { @@ -377,11 +383,15 @@ internal class DefaultGiftCardDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createGiftCardDelegate() - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegate.kt b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegate.kt index c438256fd5..39b7319d5b 100644 --- a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegate.kt +++ b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegate.kt @@ -35,6 +35,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions", "LongParameterList") internal class DefaultIssuerListDelegate< @@ -68,7 +69,7 @@ internal class DefaultIssuerListDelegate< private val _viewFlow: MutableStateFlow = MutableStateFlow(getIssuerListComponentViewType()) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -121,10 +122,12 @@ internal class DefaultIssuerListDelegate< onInputDataChanged() } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/issuer-list/src/test/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegateTest.kt b/issuer-list/src/test/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegateTest.kt index ce91016bae..b6bccd8d98 100644 --- a/issuer-list/src/test/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegateTest.kt +++ b/issuer-list/src/test/java/com/adyen/checkout/issuerlist/internal/ui/DefaultIssuerListDelegateTest.kt @@ -30,6 +30,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -46,7 +48,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -306,11 +311,15 @@ internal class DefaultIssuerListDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(PaymentMethodTypes.IDEAL) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createIssuerListDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(PaymentMethodTypes.IDEAL) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegate.kt b/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegate.kt index 344bbc1d18..5a5b579712 100644 --- a/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegate.kt +++ b/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegate.kt @@ -35,6 +35,7 @@ import com.adyen.checkout.ui.core.internal.util.CountryUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultMBWayDelegate( @@ -59,7 +60,7 @@ internal class DefaultMBWayDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(MbWayComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow @@ -78,8 +79,8 @@ internal class DefaultMBWayDelegate( adyenLog(AdyenLogLevel.VERBOSE) { "initializeAnalytics" } analyticsManager.initialize(this, coroutineScope) - val event = GenericEvents.rendered(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) + val renderedEvent = GenericEvents.rendered(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(renderedEvent) } override fun observe( @@ -166,10 +167,12 @@ internal class DefaultMBWayDelegate( return countries.firstOrNull { it.isoCode == ISO_CODE_PORTUGAL } ?: countries.firstOrNull() } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state) } diff --git a/mbway/src/test/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegateTest.kt b/mbway/src/test/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegateTest.kt index 2cc1edbdb4..f659b8f7a6 100644 --- a/mbway/src/test/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegateTest.kt +++ b/mbway/src/test/java/com/adyen/checkout/mbway/internal/ui/DefaultMBWayDelegateTest.kt @@ -27,6 +27,8 @@ import com.adyen.checkout.mbway.mbWay import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -42,7 +44,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -269,11 +274,15 @@ internal class DefaultMBWayDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createMBWayDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt index 4ce120e348..219dca1961 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt +++ b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @Suppress("TooManyFunctions", "LongParameterList") @@ -80,7 +81,7 @@ internal class DefaultOnlineBankingDelegate< private val _viewFlow: MutableStateFlow = MutableStateFlow(OnlineBankingComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -176,10 +177,12 @@ internal class DefaultOnlineBankingDelegate< } } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt b/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt index bd1eb44666..d881df63ab 100644 --- a/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt +++ b/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt @@ -29,6 +29,8 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.adyen.checkout.ui.core.internal.util.PdfOpener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -46,7 +48,9 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Locale @@ -250,11 +254,15 @@ internal class DefaultOnlineBankingDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createOnlineBankingDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegate.kt b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegate.kt index 08d0646634..b7a831d553 100644 --- a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegate.kt +++ b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegate.kt @@ -35,6 +35,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultPayByBankDelegate( @@ -59,7 +60,7 @@ internal class DefaultPayByBankDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(null) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -186,10 +187,12 @@ internal class DefaultPayByBankDelegate( } } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/paybybank/src/test/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegateTest.kt b/paybybank/src/test/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegateTest.kt index 3dc22356a7..f285aa49ba 100644 --- a/paybybank/src/test/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegateTest.kt +++ b/paybybank/src/test/java/com/adyen/checkout/paybybank/internal/ui/DefaultPayByBankDelegateTest.kt @@ -29,6 +29,8 @@ import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -45,7 +47,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -262,11 +267,15 @@ internal class DefaultPayByBankDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createPayByBankDelegate(issuers = emptyList()) + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegate.kt b/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegate.kt index 66d0be7204..2dd71c2aad 100644 --- a/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegate.kt +++ b/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegate.kt @@ -33,6 +33,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultSepaDelegate( @@ -57,7 +58,7 @@ internal class DefaultSepaDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(SepaComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow override val uiEventFlow: Flow = submitHandler.uiEventFlow @@ -140,10 +141,12 @@ internal class DefaultSepaDelegate( ) } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/sepa/src/test/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegateTest.kt b/sepa/src/test/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegateTest.kt index 5f9a0ad68a..e69d0304d0 100644 --- a/sepa/src/test/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegateTest.kt +++ b/sepa/src/test/java/com/adyen/checkout/sepa/internal/ui/DefaultSepaDelegateTest.kt @@ -29,6 +29,8 @@ import com.adyen.checkout.sepa.sepa import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions @@ -44,7 +46,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -196,11 +201,15 @@ internal class DefaultSepaDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createSepaDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt index 2b8788813d..2511666ee7 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt @@ -33,6 +33,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultTwintDelegate( @@ -54,7 +55,7 @@ internal class DefaultTwintDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(TwintComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override fun initialize(coroutineScope: CoroutineScope) { submitHandler.initialize(coroutineScope, componentStateFlow) @@ -149,6 +150,11 @@ internal class DefaultTwintDelegate( private fun shouldStorePaymentMethod(): Boolean = componentParams.showStorePaymentField && outputData.isStorePaymentSelected + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + } + override fun onSubmit() { if (isConfirmationRequired()) { initiatePayment() @@ -156,9 +162,6 @@ internal class DefaultTwintDelegate( } private fun initiatePayment() { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - _viewFlow.tryEmit(PaymentInProgressViewType) val state = _componentStateFlow.value diff --git a/twint/src/test/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegateTest.kt b/twint/src/test/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegateTest.kt index 040e0d016c..2b53883089 100644 --- a/twint/src/test/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegateTest.kt +++ b/twint/src/test/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegateTest.kt @@ -22,6 +22,8 @@ import com.adyen.checkout.twint.twint import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -39,8 +41,11 @@ import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -294,20 +299,6 @@ internal class DefaultTwintDelegateTest( analyticsManager.assertLastEventNotEquals(expectedEvent) } - @Test - fun `when delegate is initialized and confirmation is not required, then submit event is tracked`() { - delegate = createDefaultTwintDelegate( - createCheckoutConfiguration(Amount("USD", 10L)) { - setShowStorePaymentField(false) - }, - ) - - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) - } - @Test fun `when component state is valid, then payment method should contain checkoutAttemptId`() = runTest { analyticsManager.setCheckoutAttemptId(TEST_CHECKOUT_ATTEMPT_ID) @@ -320,11 +311,15 @@ internal class DefaultTwintDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createDefaultTwintDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test diff --git a/upi/src/main/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegate.kt b/upi/src/main/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegate.kt index 5549d05871..64c84c066c 100644 --- a/upi/src/main/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegate.kt +++ b/upi/src/main/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegate.kt @@ -42,6 +42,7 @@ import com.adyen.checkout.upi.internal.ui.model.mapToSelectedMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class DefaultUPIDelegate( @@ -66,7 +67,7 @@ internal class DefaultUPIDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(UPIComponentViewType) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow + override val submitFlow: Flow = getTrackedSubmitFlow() override val uiStateFlow: Flow = submitHandler.uiStateFlow @@ -312,10 +313,12 @@ internal class DefaultUPIDelegate( return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } - override fun onSubmit() { + private fun getTrackedSubmitFlow() = submitHandler.submitFlow.onEach { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + } + override fun onSubmit() { submitHandler.onSubmit(_componentStateFlow.value) } diff --git a/upi/src/test/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegateTest.kt b/upi/src/test/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegateTest.kt index 40e23abccf..e43678c429 100644 --- a/upi/src/test/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegateTest.kt +++ b/upi/src/test/java/com/adyen/checkout/upi/internal/ui/DefaultUPIDelegateTest.kt @@ -38,6 +38,8 @@ import com.adyen.checkout.upi.upi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -54,7 +56,10 @@ import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @@ -555,11 +560,15 @@ internal class DefaultUPIDelegateTest( } @Test - fun `when onSubmit is called, then submit event is tracked`() { - delegate.onSubmit() - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) + fun `when submitFlow emits an event, then submit event is tracked`() = runTest { + val submitFlow = flow { emit(mock()) } + whenever(submitHandler.submitFlow) doReturn submitFlow + val delegate = createUPIDelegate() + + delegate.submitFlow.collectLatest { + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test From 15b81684e902242952bc41f56f26e4ff08cd5ae0 Mon Sep 17 00:00:00 2001 From: josephj Date: Wed, 11 Dec 2024 13:04:12 +0100 Subject: [PATCH 71/95] Automatically update verification metadata when AGP is updated --- .github/workflows/update_verification_metadata.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update_verification_metadata.yml b/.github/workflows/update_verification_metadata.yml index 35df6f7f15..d24769e38c 100644 --- a/.github/workflows/update_verification_metadata.yml +++ b/.github/workflows/update_verification_metadata.yml @@ -1,17 +1,21 @@ name: Update verification metadata on: + push: + branches: + - 'renovate/android.gradle.plugin' workflow_dispatch: jobs: update-verification-metadata: name: Update verification metadata runs-on: ubuntu-latest + if: ${{ !contains(github.event.commits[0].message, '[skip update-verification-metadata]') }} steps: - uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + token: ${{ secrets.ADYEN_AUTOMATION_BOT_ACCESS_TOKEN }} - name: Set up JDK uses: actions/setup-java@v4 @@ -32,8 +36,10 @@ jobs: - name: Commit run: | - git config --global user.name "${{ github.actor }}" - git config --global user.email "${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ secrets.ADYEN_AUTOMATION_BOT_USER }}" + git config user.email "${{ secrets.ADYEN_AUTOMATION_BOT_NO_REPLY_EMAIL }}" git add . - git diff-index --quiet HEAD || git commit -am 'Update verification metadata' + git diff-index --quiet HEAD || git commit -m "Update verification metadata + + [skip update-verification-metadata]" git push From 89fe9e541daa2ec7bb95c837670ebbc1c560fcc2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:25:27 +0000 Subject: [PATCH 72/95] Update android.gradle.plugin to v8.7.3 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 450 +++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da5c5691fc..0c7b919d86 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ version-code = "1" version-name = "5.8.0" # Build script -android-gradle-plugin = "8.7.2" +android-gradle-plugin = "8.7.3" kotlin = "1.9.25" ksp = "1.9.25-1.0.20" dokka = "1.9.20" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 213fccdb8c..54afed7346 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3133,6 +3133,14 @@ + + + + + + + + @@ -3197,6 +3205,14 @@ + + + + + + + + @@ -3261,6 +3277,14 @@ + + + + + + + + @@ -5609,6 +5633,14 @@ + + + + + + + + @@ -5673,6 +5705,14 @@ + + + + + + + + @@ -5713,6 +5753,11 @@ + + + + + @@ -5777,6 +5822,14 @@ + + + + + + + + @@ -5817,6 +5870,11 @@ + + + + + @@ -5897,6 +5955,14 @@ + + + + + + + + @@ -5977,6 +6043,14 @@ + + + + + + + + @@ -6057,6 +6131,14 @@ + + + + + + + + @@ -6217,6 +6299,14 @@ + + + + + + + + @@ -6297,6 +6387,14 @@ + + + + + + + + @@ -6377,6 +6475,14 @@ + + + + + + + + @@ -6441,6 +6547,14 @@ + + + + + + + + @@ -6521,6 +6635,14 @@ + + + + + + + + @@ -6601,6 +6723,14 @@ + + + + + + + + @@ -6681,6 +6811,14 @@ + + + + + + + + @@ -6849,6 +6987,14 @@ + + + + + + + + @@ -6913,6 +7059,14 @@ + + + + + + + + @@ -6977,6 +7131,14 @@ + + + + + + + + @@ -7041,6 +7203,14 @@ + + + + + + + + @@ -7105,6 +7275,14 @@ + + + + + + + + @@ -7185,6 +7363,14 @@ + + + + + + + + @@ -7249,6 +7435,14 @@ + + + + + + + + @@ -7337,6 +7531,14 @@ + + + + + + + + @@ -7401,6 +7603,14 @@ + + + + + + + + @@ -7465,6 +7675,14 @@ + + + + + + + + @@ -7545,6 +7763,14 @@ + + + + + + + + @@ -7649,6 +7875,14 @@ + + + + + + + + @@ -7705,6 +7939,14 @@ + + + + + + + + @@ -8025,6 +8267,14 @@ + + + + + + + + @@ -8409,6 +8659,14 @@ + + + + + + + + @@ -8513,6 +8771,14 @@ + + + + + + + + @@ -8569,6 +8835,14 @@ + + + + + + + + @@ -8633,6 +8907,14 @@ + + + + + + + + @@ -8689,6 +8971,14 @@ + + + + + + + + @@ -8753,6 +9043,14 @@ + + + + + + + + @@ -8769,6 +9067,14 @@ + + + + + + + + @@ -8785,6 +9091,14 @@ + + + + + + + + @@ -8841,6 +9155,14 @@ + + + + + + + + @@ -8905,6 +9227,14 @@ + + + + + + + + @@ -8961,6 +9291,14 @@ + + + + + + + + @@ -9025,6 +9363,14 @@ + + + + + + + + @@ -9081,6 +9427,14 @@ + + + + + + + + @@ -9145,6 +9499,14 @@ + + + + + + + + @@ -9201,6 +9563,14 @@ + + + + + + + + @@ -9257,6 +9627,14 @@ + + + + + + + + @@ -9313,6 +9691,14 @@ + + + + + + + + @@ -9377,6 +9763,14 @@ + + + + + + + + @@ -9433,6 +9827,14 @@ + + + + + + + + @@ -9497,6 +9899,14 @@ + + + + + + + + @@ -9553,6 +9963,14 @@ + + + + + + + + @@ -9617,6 +10035,14 @@ + + + + + + + + @@ -9673,6 +10099,14 @@ + + + + + + + + @@ -9737,6 +10171,14 @@ + + + + + + + + @@ -9793,6 +10235,14 @@ + + + + + + + + From 23bbee70e04d5b87a9fbc30771bff15e5f32e0d7 Mon Sep 17 00:00:00 2001 From: AdyenAutomationBot <38424300+AdyenAutomationBot@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:17:01 +0000 Subject: [PATCH 73/95] Update verification metadata [skip update-verification-metadata] --- gradle/verification-metadata.xml | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 54afed7346..58af97f3a6 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -6219,6 +6219,14 @@ + + + + + + + + @@ -6907,6 +6915,14 @@ + + + + + + + + @@ -8027,6 +8043,14 @@ + + + + + + + + @@ -8107,6 +8131,14 @@ + + + + + + + + @@ -8187,6 +8219,14 @@ + + + + + + + + @@ -8355,6 +8395,14 @@ + + + + + + + + @@ -8435,6 +8483,14 @@ + + + + + + + + @@ -8515,6 +8571,14 @@ + + + + + + + + @@ -8579,6 +8643,14 @@ + + + + + + + + From ef95a25dcadbd61d2f891152b18472fb028048db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 01:07:05 +0000 Subject: [PATCH 74/95] Update dependency com.squareup.moshi:moshi-adapters to v1.15.2 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c7b919d86..10d9dc767a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,7 +56,7 @@ wechat-pay = "6.8.0" # Example app leak-canary = "2.14" -moshi-adapters = "1.15.1" +moshi-adapters = "1.15.2" moshi-kotlin-adapter = "1.15.1" okhttp-logging = "4.12.0" preference = "1.2.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 58af97f3a6..205695fdef 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12603,6 +12603,14 @@ + + + + + + + + @@ -12624,6 +12632,14 @@ + + + + + + + + From 8d2324767db976120bba6bd0c264bf952a7d35a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 08:59:55 +0000 Subject: [PATCH 75/95] Update dependency com.squareup.moshi:moshi-kotlin to v1.15.2 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 10d9dc767a..baf85d5aa0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,7 +57,7 @@ wechat-pay = "6.8.0" # Example app leak-canary = "2.14" moshi-adapters = "1.15.2" -moshi-kotlin-adapter = "1.15.1" +moshi-kotlin-adapter = "1.15.2" okhttp-logging = "4.12.0" preference = "1.2.1" retrofit2 = "2.11.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 205695fdef..6e7d3bf229 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12672,6 +12672,14 @@ + + + + + + + + From 7cd03f802e35062188a09ee6342f10269863dc78 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:22:32 +0000 Subject: [PATCH 76/95] Update junit5 monorepo to v5.11.4 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index baf85d5aa0..9c753be739 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,7 @@ barista = "4.3.0" espresso = "3.6.1" json = "20240303" jose4j = "0.9.6" -junit-jupiter = "5.11.3" +junit-jupiter = "5.11.4" konsist = "0.16.1" lint = "31.7.2" mockito-kotlin = "5.4.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 6e7d3bf229..c76fe7c5ee 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -17348,6 +17348,14 @@ + + + + + + + + @@ -17404,6 +17412,14 @@ + + + + + + + + @@ -17444,6 +17460,14 @@ + + + + + + + + @@ -17484,6 +17508,14 @@ + + + + + + + + @@ -17524,6 +17556,14 @@ + + + + + + + + @@ -17564,6 +17604,14 @@ + + + + + + + + @@ -17604,6 +17652,14 @@ + + + + + + + + From 5ad58b9cb1728169991f637334364ab21fcbaef5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:19:18 +0000 Subject: [PATCH 77/95] Update lint to v31.7.3 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c753be739..a6e8ad7fb9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -70,7 +70,7 @@ json = "20240303" jose4j = "0.9.6" junit-jupiter = "5.11.4" konsist = "0.16.1" -lint = "31.7.2" +lint = "31.7.3" mockito-kotlin = "5.4.0" mockito = "5.14.2" robolectric = "4.13" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c76fe7c5ee..115c6947aa 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -8779,6 +8779,14 @@ + + + + + + + + From dce892bcfe11f895a91d29ef9ed8d7e59c6c60bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:56:46 +0000 Subject: [PATCH 78/95] Update JamesIves/github-pages-deploy-action action to v4.7.2 --- .github/workflows/publish_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index eacb82dc5f..a5406c054b 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -38,7 +38,7 @@ jobs: # Deploy to GitHub Pages - name: Deploy GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4.6.9 + uses: JamesIves/github-pages-deploy-action@v4.7.2 with: BRANCH: gh-pages FOLDER: build/docs/ From f1e700af8dcc67852180b1bc765cbc96ddfcf5bd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:44:33 +0000 Subject: [PATCH 79/95] Update dependency androidx.compose:compose-bom to v2024.12.01 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 292 +++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6e8ad7fb9..c54cac849f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ constraintlayout = "2.2.0" # Compose dependencies compose-activity = "1.9.3" -compose-bom = "2024.11.00" +compose-bom = "2024.12.01" compose-hilt = "1.2.0" compose-viewmodel = "2.8.7" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 115c6947aa..a7123b2270 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -705,6 +705,11 @@ + + + + + @@ -761,6 +766,14 @@ + + + + + + + + @@ -817,6 +830,14 @@ + + + + + + + + @@ -873,6 +894,14 @@ + + + + + + + + @@ -929,6 +958,14 @@ + + + + + + + + @@ -1025,6 +1062,14 @@ + + + + + + + + @@ -1081,6 +1126,14 @@ + + + + + + + + @@ -1137,6 +1190,14 @@ + + + + + + + + @@ -1193,6 +1254,14 @@ + + + + + + + + @@ -1228,6 +1297,11 @@ + + + + + @@ -1284,6 +1358,14 @@ + + + + + + + + @@ -1340,6 +1422,14 @@ + + + + + + + + @@ -1396,6 +1486,14 @@ + + + + + + + + @@ -1452,6 +1550,14 @@ + + + + + + + + @@ -1508,6 +1614,14 @@ + + + + + + + + @@ -1633,6 +1747,14 @@ + + + + + + + + @@ -1689,6 +1811,14 @@ + + + + + + + + @@ -1745,6 +1875,14 @@ + + + + + + + + @@ -1801,6 +1939,14 @@ + + + + + + + + @@ -1857,6 +2003,14 @@ + + + + + + + + @@ -1913,6 +2067,14 @@ + + + + + + + + @@ -1969,6 +2131,14 @@ + + + + + + + + @@ -2025,6 +2195,14 @@ + + + + + + + + @@ -2081,6 +2259,14 @@ + + + + + + + + @@ -2137,6 +2323,14 @@ + + + + + + + + @@ -2193,6 +2387,14 @@ + + + + + + + + @@ -2249,6 +2451,14 @@ + + + + + + + + @@ -2305,6 +2515,14 @@ + + + + + + + + @@ -2340,6 +2558,11 @@ + + + + + @@ -2396,6 +2619,14 @@ + + + + + + + + @@ -2431,6 +2662,11 @@ + + + + + @@ -2487,6 +2723,14 @@ + + + + + + + + @@ -2543,6 +2787,14 @@ + + + + + + + + @@ -2599,6 +2851,14 @@ + + + + + + + + @@ -2655,6 +2915,14 @@ + + + + + + + + @@ -2711,6 +2979,14 @@ + + + + + + + + @@ -2767,6 +3043,14 @@ + + + + + + + + @@ -2823,6 +3107,14 @@ + + + + + + + + From 97f2db2de7798ba94200fb9e1f7b67a5be7a3913 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:52:55 +0000 Subject: [PATCH 80/95] Update dependency com.lemonappdev:konsist to v0.17.3 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c54cac849f..8b84e2821c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,7 +69,7 @@ espresso = "3.6.1" json = "20240303" jose4j = "0.9.6" junit-jupiter = "5.11.4" -konsist = "0.16.1" +konsist = "0.17.3" lint = "31.7.3" mockito-kotlin = "5.4.0" mockito = "5.14.2" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a7123b2270..fe8156fc87 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12058,6 +12058,14 @@ + + + + + + + + @@ -16790,6 +16798,14 @@ + + + + + + + + @@ -16893,6 +16909,14 @@ + + + + + + + + From 7939fe18f24603b37adf86893fda5ebbac8a9872 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:22:03 +0000 Subject: [PATCH 81/95] Update dependency com.pinterest.ktlint:ktlint-cli to v1.5.0 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 269 +++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b84e2821c..3553e77700 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ compose-compiler = "1.5.15" # Code quality detekt = "1.23.7" jacoco = "0.8.12" -ktlint = "1.4.1" +ktlint = "1.5.0" sonarqube = "5.1.0.4882" binary-compatibility-validator = "0.16.3" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index fe8156fc87..00f41e949f 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -10724,11 +10724,21 @@ + + + + + + + + + + @@ -10737,6 +10747,14 @@ + + + + + + + + @@ -10761,6 +10779,14 @@ + + + + + + + + @@ -10815,11 +10841,21 @@ + + + + + + + + + + @@ -10828,6 +10864,14 @@ + + + + + + + + @@ -10852,11 +10896,24 @@ + + + + + + + + + + + + + @@ -10865,11 +10922,24 @@ + + + + + + + + + + + + + @@ -10878,11 +10948,24 @@ + + + + + + + + + + + + + @@ -10891,6 +10974,14 @@ + + + + + + + + @@ -12098,6 +12189,14 @@ + + + + + + + + @@ -12130,6 +12229,14 @@ + + + + + + + + @@ -12162,6 +12269,14 @@ + + + + + + + + @@ -12194,6 +12309,14 @@ + + + + + + + + @@ -12226,6 +12349,14 @@ + + + + + + + + @@ -12258,6 +12389,14 @@ + + + + + + + + @@ -12290,6 +12429,14 @@ + + + + + + + + @@ -12322,6 +12469,14 @@ + + + + + + + + @@ -12354,6 +12509,14 @@ + + + + + + + + @@ -12386,6 +12549,14 @@ + + + + + + + + @@ -12426,6 +12597,14 @@ + + + + + + + + @@ -12466,6 +12645,14 @@ + + + + + + + + @@ -12498,6 +12685,14 @@ + + + + + + + + @@ -12538,6 +12733,14 @@ + + + + + + + + @@ -12578,6 +12781,14 @@ + + + + + + + + @@ -13246,6 +13457,11 @@ + + + + + @@ -13286,6 +13502,14 @@ + + + + + + + + @@ -13394,6 +13618,14 @@ + + + + + + + + @@ -15636,6 +15868,14 @@ + + + + + + + + @@ -15764,6 +16004,14 @@ + + + + + + + + @@ -16329,6 +16577,14 @@ + + + + + + + + @@ -16595,6 +16851,14 @@ + + + + + + + + @@ -16705,6 +16969,11 @@ + + + + + From 4683b4f58553a122474e0a1294d61971f7547d54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:14:48 +0000 Subject: [PATCH 82/95] Update dependency gradle to v8.11.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72b8b..e2847c8200 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 5e2d36ccba2ac29be5621b3d5c45ca57e07ea80e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:58:27 +0000 Subject: [PATCH 83/95] Update dependency org.robolectric:robolectric to v4.14.1 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 231 +++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3553e77700..a6ca85b775 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -73,7 +73,7 @@ konsist = "0.17.3" lint = "31.7.3" mockito-kotlin = "5.4.0" mockito = "5.14.2" -robolectric = "4.13" +robolectric = "4.14.1" test-ext = "1.2.1" test-rules = "1.6.1" turbine = "1.2.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 00f41e949f..50d9fb2690 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -5323,6 +5323,14 @@ + + + + + + + + @@ -11719,6 +11727,14 @@ + + + + + + + + @@ -11751,6 +11767,14 @@ + + + + + + + + @@ -11797,6 +11821,11 @@ + + + + + @@ -11807,6 +11836,11 @@ + + + + + @@ -11897,6 +11931,17 @@ + + + + + + + + + + + @@ -11927,6 +11972,11 @@ + + + + + @@ -11959,6 +12009,14 @@ + + + + + + + + @@ -12083,6 +12141,19 @@ + + + + + + + + + + + + + @@ -14960,6 +15031,14 @@ + + + + + + + + @@ -18489,6 +18568,14 @@ + + + + + + + + @@ -18513,6 +18600,14 @@ + + + + + + + + @@ -18560,6 +18655,14 @@ + + + + + + + + @@ -18592,6 +18695,14 @@ + + + + + + + + @@ -18608,6 +18719,14 @@ + + + + + + + + @@ -18640,6 +18759,14 @@ + + + + + + + + @@ -18672,6 +18799,14 @@ + + + + + + + + @@ -18704,6 +18839,14 @@ + + + + + + + + @@ -18720,6 +18863,14 @@ + + + + + + + + @@ -18768,6 +18919,14 @@ + + + + + + + + @@ -18800,6 +18959,14 @@ + + + + + + + + @@ -18832,6 +18999,14 @@ + + + + + + + + @@ -18864,6 +19039,14 @@ + + + + + + + + @@ -18896,6 +19079,14 @@ + + + + + + + + @@ -18928,6 +19119,14 @@ + + + + + + + + @@ -18960,6 +19159,14 @@ + + + + + + + + @@ -19016,6 +19223,14 @@ + + + + + + + + @@ -19048,6 +19263,14 @@ + + + + + + + + @@ -19209,5 +19432,13 @@ + + + + + + + + From 71216444028feffa4dd8a2fcb54e383a8866c061 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 18 Dec 2024 10:36:55 +0100 Subject: [PATCH 84/95] Adjust error events for 3ds2 and track api error COAND-1007 --- .../adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt | 6 +++--- .../adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 840f4b8094..83f38fda4a 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -269,8 +269,8 @@ internal class DefaultAdyen3DS2Delegate( val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> adyenLog(AdyenLogLevel.ERROR, throwable) { "Unexpected uncaught 3DS2 Exception" } trackFingerprintErrorEvent( - errorEvent = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, - message = "Fingerprint creation failed because of uncaught exception", + errorEvent = ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, + message = "Fingerprint handling failed because of uncaught exception", ) emitError(CheckoutException("Unexpected 3DS2 exception.", throwable)) } @@ -436,7 +436,7 @@ internal class DefaultAdyen3DS2Delegate( .fold( onSuccess = { result -> onSubmitFingerprintResult(result, activity) }, onFailure = { e -> - trackFingerprintErrorEvent(ErrorEvent.THREEDS2_FINGERPRINT_HANDLING) + trackFingerprintErrorEvent(ErrorEvent.API_THREEDS2) emitError(ComponentException("Unable to submit fingerprint", e)) }, ) diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index df21e5c214..cec5f7ea90 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -752,7 +752,7 @@ internal class DefaultAdyen3DS2DelegateTest( delegate.identifyShopper(Activity(), encodedJson, false) val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( - event = ErrorEvent.THREEDS2_FINGERPRINT_CREATION, + event = ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, ) analyticsManager.assertLastEventEquals(expectedEvent) } @@ -837,7 +837,7 @@ internal class DefaultAdyen3DS2DelegateTest( delegate.identifyShopper(Activity(), encodedJson, true) val expectedEvent = ThreeDS2Events.threeDS2FingerprintError( - event = ErrorEvent.THREEDS2_FINGERPRINT_HANDLING, + event = ErrorEvent.API_THREEDS2, ) analyticsManager.assertLastEventEquals(expectedEvent) } From 99118e4679b20344cc6f551e9208515d2675f08a Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 18 Dec 2024 10:54:16 +0100 Subject: [PATCH 85/95] Track api error event for native redirect COAND-1007 --- .../internal/ui/DefaultRedirectDelegate.kt | 11 +++++++++ .../ui/DefaultRedirectDelegateTest.kt | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegate.kt b/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegate.kt index cbb0ace41b..a844e690dc 100644 --- a/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegate.kt +++ b/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegate.kt @@ -192,8 +192,10 @@ constructor( val detailsJson = NativeRedirectResponse.SERIALIZER.serialize(response) emitDetails(detailsJson) } catch (e: HttpException) { + trackNativeRedirectError("Network error") emitError(e) } catch (e: ModelSerializationException) { + trackNativeRedirectError("Serialization error") emitError(e) } } @@ -207,6 +209,15 @@ constructor( redirectHandler.setOnRedirectListener(listener) } + private fun trackNativeRedirectError(message: String) { + val event = GenericEvents.error( + component = action?.paymentMethodType.orEmpty(), + event = ErrorEvent.API_NATIVE_REDIRECT, + message = message, + ) + analyticsManager?.trackEvent(event) + } + private fun emitError(e: CheckoutException) { exceptionChannel.trySend(e) clearState() diff --git a/redirect/src/test/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegateTest.kt b/redirect/src/test/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegateTest.kt index 8643e3c165..ddbe2234a8 100644 --- a/redirect/src/test/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegateTest.kt +++ b/redirect/src/test/java/com/adyen/checkout/redirect/internal/ui/DefaultRedirectDelegateTest.kt @@ -236,6 +236,30 @@ internal class DefaultRedirectDelegateTest( ) analyticsManager.assertLastEventEquals(expectedEvent) } + + @ParameterizedTest + @MethodSource("com.adyen.checkout.redirect.internal.ui.DefaultRedirectDelegateTest#errorSource") + fun `when native redirect is handled and error is thrown, then an error event is tracked`(error: Exception) = + runTest { + whenever(nativeRedirectService.makeNativeRedirect(any(), any())) doAnswer { throw error } + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.handleAction( + action = RedirectAction( + paymentMethodType = TEST_PAYMENT_METHOD_TYPE, + type = ActionTypes.NATIVE_REDIRECT, + nativeRedirectData = "testData", + ), + activity = Activity(), + ) + + delegate.handleIntent(Intent()) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = ErrorEvent.API_NATIVE_REDIRECT, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } } @Test From 2d311a75b8d787aab247dc567f7644fcc41a5565 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 18 Dec 2024 11:27:57 +0100 Subject: [PATCH 86/95] Track api error event for fetching public key COAND-1007 --- .../ach/internal/ui/DefaultACHDirectDebitDelegate.kt | 4 ++++ .../ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt | 8 ++++++++ .../checkout/card/internal/ui/DefaultCardDelegate.kt | 4 ++++ .../adyen/checkout/card/internal/ui/StoredCardDelegate.kt | 3 +++ .../checkout/card/internal/ui/DefaultCardDelegateTest.kt | 8 ++++++++ .../checkout/card/internal/ui/StoredCardDelegateTest.kt | 8 ++++++++ .../giftcard/internal/ui/DefaultGiftCardDelegate.kt | 4 ++++ .../giftcard/internal/ui/DefaultGiftCardDelegateTest.kt | 8 ++++++++ 8 files changed, 47 insertions(+) diff --git a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt index 624698b320..df8a811199 100644 --- a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt +++ b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegate.kt @@ -201,6 +201,10 @@ internal class DefaultACHDirectDebitDelegate( }, onFailure = { e -> adyenLog(AdyenLogLevel.ERROR) { "Unable to fetch public key" } + + val event = GenericEvents.error(paymentMethod.type.orEmpty(), ErrorEvent.API_PUBLIC_KEY) + analyticsManager.trackEvent(event) + exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) }, ) diff --git a/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt b/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt index 7f10877a75..32e8a52df0 100644 --- a/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt +++ b/ach/src/test/java/com/adyen/checkout/ach/internal/ui/DefaultACHDirectDebitDelegateTest.kt @@ -690,6 +690,14 @@ internal class DefaultACHDirectDebitDelegateTest( assertEquals(TEST_CHECKOUT_ATTEMPT_ID, componentState.data.paymentMethod?.checkoutAttemptId) } + @Test + fun `when fetching the public key fails, then an error event is tracked`() = runTest { + publicKeyRepository.shouldReturnError = true + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.API_PUBLIC_KEY) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 6cd066770d..da82dceb2b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -219,6 +219,10 @@ class DefaultCardDelegate( }, onFailure = { e -> adyenLog(AdyenLogLevel.ERROR) { "Unable to fetch public key" } + + val event = GenericEvents.error(paymentMethod.type.orEmpty(), ErrorEvent.API_PUBLIC_KEY) + analyticsManager.trackEvent(event) + exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) }, ) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index 3e2f07677b..95abc956b9 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -198,6 +198,9 @@ internal class StoredCardDelegate( updateComponentState(outputData) }, onFailure = { e -> + val event = GenericEvents.error(storedPaymentMethod.type.orEmpty(), ErrorEvent.API_PUBLIC_KEY) + analyticsManager.trackEvent(event) + exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) }, ) diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index 289ea5d5fa..36a26ca18b 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -1095,6 +1095,14 @@ internal class DefaultCardDelegateTest( } } + @Test + fun `when fetching the public key fails, then an error event is tracked`() = runTest { + publicKeyRepository.shouldReturnError = true + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val expectedEvent = GenericEvents.error(PaymentMethodTypes.SCHEME, ErrorEvent.API_PUBLIC_KEY) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt index e1b32b3719..3625c39e43 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt @@ -485,6 +485,14 @@ internal class StoredCardDelegateTest( } } + @Test + fun `when fetching the public key fails, then an error event is tracked`() = runTest { + publicKeyRepository.shouldReturnError = true + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val expectedEvent = GenericEvents.error(CardPaymentMethod.PAYMENT_METHOD_TYPE, ErrorEvent.API_PUBLIC_KEY) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() diff --git a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt index 5d985c1716..3b0d63d3d9 100644 --- a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt +++ b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegate.kt @@ -129,6 +129,10 @@ class DefaultGiftCardDelegate( }, onFailure = { e -> adyenLog(AdyenLogLevel.ERROR) { "Unable to fetch public key" } + + val event = GenericEvents.error(paymentMethod.type.orEmpty(), ErrorEvent.API_PUBLIC_KEY) + analyticsManager.trackEvent(event) + exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) }, ) diff --git a/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt b/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt index 27d85525e5..061a0db4b4 100644 --- a/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt +++ b/giftcard/src/test/java/com/adyen/checkout/giftcard/internal/ui/DefaultGiftCardDelegateTest.kt @@ -410,6 +410,14 @@ internal class DefaultGiftCardDelegateTest( } } + @Test + fun `when fetching the public key fails, then an error event is tracked`() = runTest { + publicKeyRepository.shouldReturnError = true + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val expectedEvent = GenericEvents.error(TEST_PAYMENT_METHOD_TYPE, ErrorEvent.API_PUBLIC_KEY) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() From 61d146e4bf1100ba8bc4bffa6f5963ff27962b28 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 18 Dec 2024 14:26:37 +0100 Subject: [PATCH 87/95] Remove events that can't be tracked on mobile COAND-1007 --- .../checkout/components/core/internal/analytics/ErrorEvent.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt index cee3cfd2fe..de3494c269 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/ErrorEvent.kt @@ -26,9 +26,7 @@ enum class ErrorEvent(val errorType: Type, val errorCode: String) { // API API_PAYMENTS(Type.API_ERROR, "620"), - API_PAYMENTS_DETAILS(Type.API_ERROR, "621"), API_THREEDS2(Type.API_ERROR, "622"), - API_ORDER(Type.API_ERROR, "623"), API_PUBLIC_KEY(Type.API_ERROR, "624"), API_NATIVE_REDIRECT(Type.API_ERROR, "625"), From b9a47e19c0d84b110d28635366eb6d13915e5f2b Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Wed, 18 Dec 2024 14:29:28 +0100 Subject: [PATCH 88/95] Remove unused google pay request code COAND-1007 --- .../com/adyen/checkout/dropin/internal/ui/DropInActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt index 237c69bc22..370c0dde13 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt @@ -728,8 +728,6 @@ internal class DropInActivity : private const val LOADING_FRAGMENT_TAG = "LOADING_DIALOG_FRAGMENT" private const val GIFT_CARD_PAYMENT_CONFIRMATION_FRAGMENT_TAG = "GIFT_CARD_PAYMENT_CONFIRMATION_FRAGMENT" - internal const val GOOGLE_PAY_REQUEST_CODE = 1 - fun createIntent( context: Context, checkoutConfiguration: CheckoutConfiguration, From e91f5fe84d5c6c15ad96d994d6a0ac5ddf9a4e7e Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 17 Dec 2024 17:24:57 +0100 Subject: [PATCH 89/95] Track error event when `/payments` call failed COAND-1007 --- .../ACHDirectDebitComponentProvider.kt | 1 + .../BacsDirectDebitComponentProvider.kt | 1 + .../provider/BcmcComponentProvider.kt | 1 + .../provider/BlikComponentProvider.kt | 2 ++ .../provider/BoletoComponentProvider.kt | 1 + .../provider/CardComponentProvider.kt | 2 ++ .../provider/CashAppPayComponentProvider.kt | 1 + drop-in/api/drop-in.api | 2 +- .../checkout/dropin/SessionDropInService.kt | 5 ++- .../service/BaseDropInServiceInterfaces.kt | 4 ++- .../dropin/internal/ui/DropInActivity.kt | 1 + .../dropin/internal/ui/DropInViewModel.kt | 1 + .../internal/ui/model/DropInActivityEvent.kt | 2 ++ .../provider/EContextComponentProvider.kt | 1 + .../provider/GiftCardComponentProvider.kt | 1 + .../provider/GooglePayComponentProvider.kt | 1 + .../provider/IdealComponentProvider.kt | 1 + .../InstantPaymentComponentProvider.kt | 1 + .../provider/IssuerListComponentProvider.kt | 1 + .../provider/MBWayComponentProvider.kt | 1 + .../MealVoucherFRComponentProvider.kt | 1 + .../OnlineBankingComponentProvider.kt | 1 + .../provider/PayByBankUSComponentProvider.kt | 2 ++ .../provider/PayByBankComponentProvider.kt | 1 + .../provider/SepaComponentProvider.kt | 1 + sessions-core/build.gradle | 1 + .../core/internal/SessionInteractor.kt | 15 +++++++- .../core/internal/SessionInteractorTest.kt | 36 +++++++++++++++++-- .../provider/TwintComponentProvider.kt | 1 + .../internal/provider/UPIComponentProvider.kt | 1 + 30 files changed, 85 insertions(+), 6 deletions(-) diff --git a/ach/src/main/java/com/adyen/checkout/ach/internal/provider/ACHDirectDebitComponentProvider.kt b/ach/src/main/java/com/adyen/checkout/ach/internal/provider/ACHDirectDebitComponentProvider.kt index f4ea620536..c781d60ac7 100644 --- a/ach/src/main/java/com/adyen/checkout/ach/internal/provider/ACHDirectDebitComponentProvider.kt +++ b/ach/src/main/java/com/adyen/checkout/ach/internal/provider/ACHDirectDebitComponentProvider.kt @@ -516,6 +516,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) return SessionComponentEventHandler( diff --git a/bacs/src/main/java/com/adyen/checkout/bacs/internal/provider/BacsDirectDebitComponentProvider.kt b/bacs/src/main/java/com/adyen/checkout/bacs/internal/provider/BacsDirectDebitComponentProvider.kt index 02ee7debd1..b8eaf9c493 100644 --- a/bacs/src/main/java/com/adyen/checkout/bacs/internal/provider/BacsDirectDebitComponentProvider.kt +++ b/bacs/src/main/java/com/adyen/checkout/bacs/internal/provider/BacsDirectDebitComponentProvider.kt @@ -215,6 +215,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt index 972fa7ba9a..e87684708a 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt @@ -267,6 +267,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( diff --git a/blik/src/main/java/com/adyen/checkout/blik/internal/provider/BlikComponentProvider.kt b/blik/src/main/java/com/adyen/checkout/blik/internal/provider/BlikComponentProvider.kt index 562e2fc2a2..ebdb49ccd3 100644 --- a/blik/src/main/java/com/adyen/checkout/blik/internal/provider/BlikComponentProvider.kt +++ b/blik/src/main/java/com/adyen/checkout/blik/internal/provider/BlikComponentProvider.kt @@ -318,6 +318,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( @@ -425,6 +426,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt index e1d2261e2e..adb7a53d3a 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt @@ -224,6 +224,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = diff --git a/card/src/main/java/com/adyen/checkout/card/internal/provider/CardComponentProvider.kt b/card/src/main/java/com/adyen/checkout/card/internal/provider/CardComponentProvider.kt index 0fdf3da6c1..b2a8e88bfa 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/provider/CardComponentProvider.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/provider/CardComponentProvider.kt @@ -291,6 +291,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, @@ -504,6 +505,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/provider/CashAppPayComponentProvider.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/provider/CashAppPayComponentProvider.kt index d076d24dce..8b61f996fb 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/provider/CashAppPayComponentProvider.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/provider/CashAppPayComponentProvider.kt @@ -472,6 +472,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) return SessionComponentEventHandler( diff --git a/drop-in/api/drop-in.api b/drop-in/api/drop-in.api index f303b01736..ff04806cfd 100644 --- a/drop-in/api/drop-in.api +++ b/drop-in/api/drop-in.api @@ -338,7 +338,7 @@ public final class com/adyen/checkout/dropin/SessionDropInResultContract : andro public class com/adyen/checkout/dropin/SessionDropInService : com/adyen/checkout/dropin/internal/service/BaseDropInService, com/adyen/checkout/dropin/SessionDropInServiceContract, com/adyen/checkout/dropin/internal/service/SessionDropInServiceInterface { public fun ()V - public final fun initialize (Lcom/adyen/checkout/sessions/core/SessionModel;Ljava/lang/String;Lcom/adyen/checkout/core/Environment;Z)V + public final fun initialize (Lcom/adyen/checkout/sessions/core/SessionModel;Ljava/lang/String;Lcom/adyen/checkout/core/Environment;ZLcom/adyen/checkout/components/core/internal/analytics/AnalyticsManager;)V public final fun isFlowTakenOver ()Z public fun onAdditionalDetails (Lcom/adyen/checkout/components/core/ActionComponentData;)Z public fun onBalanceCheck (Lcom/adyen/checkout/components/core/PaymentComponentState;)Z diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/SessionDropInService.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/SessionDropInService.kt index a3e438a23d..ed32883e0f 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/SessionDropInService.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/SessionDropInService.kt @@ -13,6 +13,7 @@ import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.OrderResponse import com.adyen.checkout.components.core.PaymentComponentState import com.adyen.checkout.components.core.StoredPaymentMethod +import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.Environment import com.adyen.checkout.core.internal.data.api.HttpClientFactory @@ -50,7 +51,8 @@ open class SessionDropInService : BaseDropInService(), SessionDropInServiceInter sessionModel: SessionModel, clientKey: String, environment: Environment, - isFlowTakenOver: Boolean + isFlowTakenOver: Boolean, + analyticsManager: AnalyticsManager, ) { val httpClient = HttpClientFactory.getHttpClient(environment) val sessionService = SessionService(httpClient) @@ -61,6 +63,7 @@ open class SessionDropInService : BaseDropInService(), SessionDropInServiceInter ), sessionModel = sessionModel, isFlowTakenOver = isFlowTakenOver, + analyticsManager = analyticsManager, ) this.isFlowTakenOver = isFlowTakenOver diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/BaseDropInServiceInterfaces.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/BaseDropInServiceInterfaces.kt index c464076a6e..f65d0f9d56 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/BaseDropInServiceInterfaces.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/BaseDropInServiceInterfaces.kt @@ -14,6 +14,7 @@ import com.adyen.checkout.components.core.LookupAddress import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentState import com.adyen.checkout.components.core.StoredPaymentMethod +import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager import com.adyen.checkout.core.Environment import com.adyen.checkout.dropin.BaseDropInServiceResult import com.adyen.checkout.sessions.core.SessionModel @@ -39,6 +40,7 @@ internal interface SessionDropInServiceInterface : BaseDropInServiceInterface { sessionModel: SessionModel, clientKey: String, environment: Environment, - isFlowTakenOver: Boolean + isFlowTakenOver: Boolean, + analyticsManager: AnalyticsManager, ) } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt index 370c0dde13..4d5bbea290 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt @@ -585,6 +585,7 @@ internal class DropInActivity : clientKey = event.clientKey, environment = event.environment, isFlowTakenOver = event.isFlowTakenOver, + analyticsManager = event.analyticsManager, ) } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModel.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModel.kt index 2e22d9d93a..d7f5264870 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModel.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModel.kt @@ -203,6 +203,7 @@ internal class DropInViewModel( clientKey = dropInParams.clientKey, environment = dropInParams.environment, isFlowTakenOver = isSessionsFlowTakenOver, + analyticsManager = analyticsManager, ) sendEvent(event) } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/model/DropInActivityEvent.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/model/DropInActivityEvent.kt index 3ad3d52952..5b5eeca3d0 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/model/DropInActivityEvent.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/model/DropInActivityEvent.kt @@ -11,6 +11,7 @@ package com.adyen.checkout.dropin.internal.ui.model import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentState import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager import com.adyen.checkout.core.Environment import com.adyen.checkout.sessions.core.SessionModel @@ -25,6 +26,7 @@ internal sealed class DropInActivityEvent { val clientKey: String, val environment: Environment, val isFlowTakenOver: Boolean, + val analyticsManager: AnalyticsManager, ) : DropInActivityEvent() } diff --git a/econtext/src/main/java/com/adyen/checkout/econtext/internal/provider/EContextComponentProvider.kt b/econtext/src/main/java/com/adyen/checkout/econtext/internal/provider/EContextComponentProvider.kt index cd384bb5d7..48edac41ac 100644 --- a/econtext/src/main/java/com/adyen/checkout/econtext/internal/provider/EContextComponentProvider.kt +++ b/econtext/src/main/java/com/adyen/checkout/econtext/internal/provider/EContextComponentProvider.kt @@ -223,6 +223,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/provider/GiftCardComponentProvider.kt b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/provider/GiftCardComponentProvider.kt index 7471f9ceb9..ce2086c22c 100644 --- a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/provider/GiftCardComponentProvider.kt +++ b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/provider/GiftCardComponentProvider.kt @@ -232,6 +232,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionsGiftCardComponentEventHandler = SessionsGiftCardComponentEventHandler( diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index 89d484ec28..5ff0f2953a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -231,6 +231,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/provider/IdealComponentProvider.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/provider/IdealComponentProvider.kt index c7decf421f..90eb7ab0ae 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/internal/provider/IdealComponentProvider.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/provider/IdealComponentProvider.kt @@ -209,6 +209,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/provider/InstantPaymentComponentProvider.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/provider/InstantPaymentComponentProvider.kt index 5c4a9973df..5a8b955bc3 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/internal/provider/InstantPaymentComponentProvider.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/provider/InstantPaymentComponentProvider.kt @@ -213,6 +213,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/provider/IssuerListComponentProvider.kt b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/provider/IssuerListComponentProvider.kt index 56e1ea09fe..0024da6209 100644 --- a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/provider/IssuerListComponentProvider.kt +++ b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/provider/IssuerListComponentProvider.kt @@ -222,6 +222,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/mbway/src/main/java/com/adyen/checkout/mbway/internal/provider/MBWayComponentProvider.kt b/mbway/src/main/java/com/adyen/checkout/mbway/internal/provider/MBWayComponentProvider.kt index 74e0e524b9..8d570d9a22 100644 --- a/mbway/src/main/java/com/adyen/checkout/mbway/internal/provider/MBWayComponentProvider.kt +++ b/mbway/src/main/java/com/adyen/checkout/mbway/internal/provider/MBWayComponentProvider.kt @@ -216,6 +216,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( diff --git a/meal-voucher-fr/src/main/java/com/adyen/checkout/mealvoucherfr/internal/provider/MealVoucherFRComponentProvider.kt b/meal-voucher-fr/src/main/java/com/adyen/checkout/mealvoucherfr/internal/provider/MealVoucherFRComponentProvider.kt index 5b5abf7e91..741d3d8c79 100644 --- a/meal-voucher-fr/src/main/java/com/adyen/checkout/mealvoucherfr/internal/provider/MealVoucherFRComponentProvider.kt +++ b/meal-voucher-fr/src/main/java/com/adyen/checkout/mealvoucherfr/internal/provider/MealVoucherFRComponentProvider.kt @@ -232,6 +232,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionsGiftCardComponentEventHandler = SessionsGiftCardComponentEventHandler( diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt index 5d4db5912f..be6eeaca76 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt +++ b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt @@ -231,6 +231,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt index c3dae00c7d..7f34fcbcc3 100644 --- a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt +++ b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt @@ -320,6 +320,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, @@ -425,6 +426,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = diff --git a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/provider/PayByBankComponentProvider.kt b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/provider/PayByBankComponentProvider.kt index 30efc33ebe..695327d899 100644 --- a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/provider/PayByBankComponentProvider.kt +++ b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/provider/PayByBankComponentProvider.kt @@ -212,6 +212,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/sepa/src/main/java/com/adyen/checkout/sepa/internal/provider/SepaComponentProvider.kt b/sepa/src/main/java/com/adyen/checkout/sepa/internal/provider/SepaComponentProvider.kt index 4c03c9b607..1b57c854e9 100644 --- a/sepa/src/main/java/com/adyen/checkout/sepa/internal/provider/SepaComponentProvider.kt +++ b/sepa/src/main/java/com/adyen/checkout/sepa/internal/provider/SepaComponentProvider.kt @@ -216,6 +216,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = SessionComponentEventHandler( sessionInteractor = sessionInteractor, diff --git a/sessions-core/build.gradle b/sessions-core/build.gradle index e0d2ad81e7..ad2db094bd 100644 --- a/sessions-core/build.gradle +++ b/sessions-core/build.gradle @@ -37,6 +37,7 @@ dependencies { //Tests testImplementation testFixtures(project(':test-core')) + testImplementation testFixtures(project(':components-core')) testImplementation libs.androidx.test.lifecycle testImplementation libs.bundles.junit testImplementation libs.bundles.kotlin.coroutines.test diff --git a/sessions-core/src/main/java/com/adyen/checkout/sessions/core/internal/SessionInteractor.kt b/sessions-core/src/main/java/com/adyen/checkout/sessions/core/internal/SessionInteractor.kt index 9cff52c7be..29000ce03b 100644 --- a/sessions-core/src/main/java/com/adyen/checkout/sessions/core/internal/SessionInteractor.kt +++ b/sessions-core/src/main/java/com/adyen/checkout/sessions/core/internal/SessionInteractor.kt @@ -15,6 +15,9 @@ import com.adyen.checkout.components.core.BalanceResult import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.OrderResponse import com.adyen.checkout.components.core.PaymentComponentState +import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager +import com.adyen.checkout.components.core.internal.analytics.ErrorEvent +import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.util.StatusResponseUtils import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.MethodNotImplementedException @@ -33,6 +36,7 @@ class SessionInteractor( private val sessionRepository: SessionRepository, sessionModel: SessionModel, isFlowTakenOver: Boolean, + private val analyticsManager: AnalyticsManager?, ) { @VisibleForTesting @@ -76,6 +80,14 @@ class SessionInteractor( } }, onFailure = { + paymentComponentState.data.paymentMethod?.type?.let { paymentMethodType -> + val event = GenericEvents.error( + component = paymentMethodType, + event = ErrorEvent.API_PAYMENTS, + ) + analyticsManager?.trackEvent(event) + } + return SessionCallResult.Payments.Error(throwable = it) }, ) @@ -260,7 +272,8 @@ class SessionInteractor( return if (!callWasHandled) { if (isFlowTakenOver) { throw MethodNotImplementedException( - "Sessions flow was already taken over in a previous call, $merchantMethodName should be implemented" + "Sessions flow was already taken over in a previous call, " + + "$merchantMethodName should be implemented", ) } else { internalCall() diff --git a/sessions-core/src/test/java/com/adyen/checkout/sessions/core/internal/SessionInteractorTest.kt b/sessions-core/src/test/java/com/adyen/checkout/sessions/core/internal/SessionInteractorTest.kt index 9cc6683837..c74c124848 100644 --- a/sessions-core/src/test/java/com/adyen/checkout/sessions/core/internal/SessionInteractorTest.kt +++ b/sessions-core/src/test/java/com/adyen/checkout/sessions/core/internal/SessionInteractorTest.kt @@ -18,6 +18,9 @@ import com.adyen.checkout.components.core.PaymentComponentData import com.adyen.checkout.components.core.PaymentMethodsApiResponse import com.adyen.checkout.components.core.action.Action import com.adyen.checkout.components.core.action.RedirectAction +import com.adyen.checkout.components.core.internal.analytics.ErrorEvent +import com.adyen.checkout.components.core.internal.analytics.GenericEvents +import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.util.StatusResponseUtils import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.sessions.core.SessionModel @@ -35,7 +38,6 @@ import com.adyen.checkout.sessions.core.internal.data.model.SessionOrderResponse import com.adyen.checkout.sessions.core.internal.data.model.SessionPaymentsResponse import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue @@ -51,16 +53,17 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever -@OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MockitoExtension::class, TestDispatcherExtension::class, LoggingExtension::class) internal class SessionInteractorTest( @Mock private val sessionRepository: SessionRepository, ) { private lateinit var sessionInteractor: SessionInteractor + private lateinit var analyticsManager: TestAnalyticsManager @BeforeEach fun before() { + analyticsManager = TestAnalyticsManager() sessionInteractor = createSessionInteractor() } @@ -715,6 +718,34 @@ internal class SessionInteractorTest( } } + @Nested + inner class AnalyticsTest { + + @Test + fun `when payment calls returns an error then error event is tracked`() = runTest { + val exception = Exception("failed for testing") + val paymentMethodType = "scheme" + val componentState = TestComponentState( + data = PaymentComponentData( + TestPaymentMethod(type = paymentMethodType), + TEST_ORDER_REQUEST, + TEST_AMOUNT, + ), + isInputValid = true, + isReady = true, + ) + whenever(sessionRepository.submitPayment(any(), any())) doReturn Result.failure(exception) + + sessionInteractor.onPaymentsCallRequested(componentState, { false }, "") + + val expectedEvent = GenericEvents.error( + component = paymentMethodType, + event = ErrorEvent.API_PAYMENTS, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + } + private fun createSessionInteractor( sessionModel: SessionModel = TEST_SESSION_MODEL, isFlowTakenOver: Boolean = false @@ -723,6 +754,7 @@ internal class SessionInteractorTest( sessionRepository = sessionRepository, sessionModel = sessionModel, isFlowTakenOver = isFlowTakenOver, + analyticsManager = analyticsManager, ) } diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/provider/TwintComponentProvider.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/provider/TwintComponentProvider.kt index 873a476983..d43d6cb0ad 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/provider/TwintComponentProvider.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/provider/TwintComponentProvider.kt @@ -459,6 +459,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) return SessionComponentEventHandler( diff --git a/upi/src/main/java/com/adyen/checkout/upi/internal/provider/UPIComponentProvider.kt b/upi/src/main/java/com/adyen/checkout/upi/internal/provider/UPIComponentProvider.kt index 6f9acdc20b..f25ddfce22 100644 --- a/upi/src/main/java/com/adyen/checkout/upi/internal/provider/UPIComponentProvider.kt +++ b/upi/src/main/java/com/adyen/checkout/upi/internal/provider/UPIComponentProvider.kt @@ -211,6 +211,7 @@ constructor( ), sessionModel = sessionSavedStateHandleContainer.getSessionModel(), isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false, + analyticsManager = analyticsManager, ) val sessionComponentEventHandler = From c04241368157df4126fbd0494288697ef246aeb2 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Thu, 9 Jan 2025 10:09:22 +0100 Subject: [PATCH 90/95] Add release notes for default branch changes COAND-1019 --- RELEASE_NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cabb84c1fe..ab6d91f712 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -29,3 +29,6 @@ | `AdyenCheckout.CashAppPay.ProgressBar` | `AdyenCheckout.ProcessingPaymentView.ProgressBar` | | `AdyenCheckout.CashAppPay.WaitingDescriptionTextView` | `AdyenCheckout.ProcessingPaymentView.WaitingDescriptionTextView` | | `cash_app_pay_waiting_text` | `checkout_processing_payment` | + +## Repository Maintenance +- We are changing the default branch of our SDK repository from `develop` to `main`. If you are using our SDK repository and working with the `develop` branch, we recommend switching to the `main` branch. The `develop` branch will be removed within the next few weeks. From ad6165563ae7d8542c259d4031cbcf2f5b23821e Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:16:49 +0100 Subject: [PATCH 91/95] Propagate additionalDetails on native 3ds2 cancel flow COAND-1057 --- .../internal/ui/DefaultAdyen3DS2Delegate.kt | 19 ++++++++++++++----- .../ui/DefaultAdyen3DS2DelegateTest.kt | 9 ++++----- RELEASE_NOTES.md | 1 + 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 83f38fda4a..6286ce8ca5 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import com.adyen.checkout.adyen3ds2.Authentication3DS2Exception -import com.adyen.checkout.adyen3ds2.Cancelled3DS2Exception import com.adyen.checkout.adyen3ds2.internal.analytics.ThreeDS2Events import com.adyen.checkout.adyen3ds2.internal.data.api.SubmitFingerprintRepository import com.adyen.checkout.adyen3ds2.internal.data.model.Adyen3DS2Serializer @@ -573,10 +572,20 @@ internal class DefaultAdyen3DS2Delegate( } } - private fun onCancelled() { + private fun onCancelled(result: ChallengeResult.Cancelled) { adyenLog(AdyenLogLevel.DEBUG) { "challenge cancelled" } - emitError(Cancelled3DS2Exception("Challenge canceled.")) - closeTransaction() + try { + val details = makeDetails(result.transactionStatus, result.additionalDetails) + emitDetails(details) + } catch (e: CheckoutException) { + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge is cancelled and details cannot be created", + ) + emitError(e) + } finally { + closeTransaction() + } } private fun onTimeout(result: ChallengeResult.Timeout) { @@ -615,7 +624,7 @@ internal class DefaultAdyen3DS2Delegate( when (result) { is ChallengeResult.Cancelled -> { trackChallengeCompletedEvent(ThreeDS2Events.Result.CANCELLED) - onCancelled() + onCancelled(result) } is ChallengeResult.Completed -> { diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index cec5f7ea90..1a099a3768 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -16,7 +16,6 @@ import android.content.Context import android.content.Intent import androidx.lifecycle.SavedStateHandle import com.adyen.checkout.adyen3ds2.Authentication3DS2Exception -import com.adyen.checkout.adyen3ds2.Cancelled3DS2Exception import com.adyen.checkout.adyen3ds2.internal.analytics.ThreeDS2Events import com.adyen.checkout.adyen3ds2.internal.data.api.SubmitFingerprintRepository import com.adyen.checkout.adyen3ds2.internal.data.model.Adyen3DS2Serializer @@ -489,8 +488,8 @@ internal class DefaultAdyen3DS2DelegateTest( } @Test - fun `cancelled, then an error is emitted`() = runTest { - val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + fun `cancelled, then details are emitted`() = runTest { + val detailsFlow = delegate.detailsFlow.test(testScheduler) delegate.onCompletion( result = ChallengeResult.Cancelled( @@ -499,11 +498,11 @@ internal class DefaultAdyen3DS2DelegateTest( ), ) - assertTrue(exceptionFlow.latestValue is Cancelled3DS2Exception) + assertNotNull(detailsFlow.latestValue.details) } @Test - fun `timedout, then details are emitted`() = runTest { + fun `timed out, then details are emitted`() = runTest { val detailsFlow = delegate.detailsFlow.test(testScheduler) delegate.onCompletion( diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ab6d91f712..d252aeed13 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -11,6 +11,7 @@ ## New - Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. - For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. +- For 3DS2 native flow, cancellations by shopper trigger `onAdditionalDetails()` now. With this change, merchants will be able to make a `/payments/details` call so that they can get more insight on the transaction. ## Fixed From 1266603db99794905d65ae80e0b2733cdbde80cb Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 14 Jan 2025 15:05:51 +0100 Subject: [PATCH 92/95] Deprecate the `Cancelled3DS2Exception` and add release notes COAND-1057 --- .../com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt | 1 + RELEASE_NOTES.md | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt index 35d3378588..5dc4f8a811 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt @@ -12,6 +12,7 @@ import com.adyen.checkout.core.exception.ComponentException /** * This exception is an indication that the 3DS2 Authentication was cancelled by the user. */ +@Deprecated("This exception is no longer in use") class Cancelled3DS2Exception(errorMessage: String) : ComponentException(errorMessage) { companion object { private const val serialVersionUID = 3858008275644429050L diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d252aeed13..cb1b7877b9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -11,19 +11,23 @@ ## New - Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. - For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. -- For 3DS2 native flow, cancellations by shopper trigger `onAdditionalDetails()` now. With this change, merchants will be able to make a `/payments/details` call so that they can get more insight on the transaction. ## Fixed ## Improved ## Changed +- For 3DS2 native flow, cancellations by shopper trigger `onAdditionalDetails()` event. You can make a `/payments/details` call to gain more insight into the transaction. + - If you already handle other 3DS2 errors by making a `/payments/details` call, then no changes are required and the specific handling for `Cancelled3DS2Exception` can be removed. + - If not yet implemented, you should update your systems to handle shopper cancellations using the new flow. - Dependency versions: | Name | Version | |--------------------------------------------------------------------------------------------------------|-------------------------------| | | | ## Deprecated +- `Cancelled3DS2Exception` is now deprecated. Shopper cancellations trigger the `onAdditionalDetails()` event, enabling a `/payments/details` call for transaction insights. +- For 3DS2 native flow, `Cancelled3DS2Exception` is deprecated. Now the shopper cancellations triggers `onAdditionalDetails()` and you can make a `/payments/details` call. - The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. | Previous | Now | |-----------------------------------------------------------|------------------------------------------------------------------| From 9e093310c54c6f5fa1902c1509a648f9e4a87fc3 Mon Sep 17 00:00:00 2001 From: Ararat Mnatsakanyan Date: Tue, 14 Jan 2025 15:05:51 +0100 Subject: [PATCH 93/95] Deprecate the `Cancelled3DS2Exception` and add release notes COAND-1057 --- RELEASE_NOTES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cb1b7877b9..730bcc27ab 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -26,8 +26,7 @@ | | | ## Deprecated -- `Cancelled3DS2Exception` is now deprecated. Shopper cancellations trigger the `onAdditionalDetails()` event, enabling a `/payments/details` call for transaction insights. -- For 3DS2 native flow, `Cancelled3DS2Exception` is deprecated. Now the shopper cancellations triggers `onAdditionalDetails()` and you can make a `/payments/details` call. +- `Cancelled3DS2Exception` is now deprecated. Shopper cancellation of the 3DS2 native flow triggers the `onAdditionalDetails()` event, enabling a `/payments/details` call for transaction insights. - The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. | Previous | Now | |-----------------------------------------------------------|------------------------------------------------------------------| From e832a1bd8ee9eb28959b945029811ec76f76400d Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 Jan 2025 13:55:52 +0100 Subject: [PATCH 94/95] Bump versions to 5.9.0 --- README.md | 10 +++++----- docs/payment-methods/GOOGLE_PAY.md | 4 ++-- example-app/build.gradle | 4 ++-- gradle/libs.versions.toml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 52e08ca74e..d81b1dbf90 100644 --- a/README.md +++ b/README.md @@ -31,23 +31,23 @@ Import the corresponding module in your `build.gradle` file. For Drop-in: ```groovy -implementation "com.adyen.checkout:drop-in-compose:5.8.0" +implementation "com.adyen.checkout:drop-in-compose:5.9.0" ``` For the Credit Card component: ```groovy -implementation "com.adyen.checkout:card:5.8.0" -implementation "com.adyen.checkout:components-compose:5.8.0" +implementation "com.adyen.checkout:card:5.9.0" +implementation "com.adyen.checkout:components-compose:5.9.0" ``` ### Without Jetpack Compose For Drop-in: ```groovy -implementation "com.adyen.checkout:drop-in:5.8.0" +implementation "com.adyen.checkout:drop-in:5.9.0" ``` For the Credit Card component: ```groovy -implementation "com.adyen.checkout:card:5.8.0" +implementation "com.adyen.checkout:card:5.9.0" ``` The library is available on [Maven Central][mavenRepo]. diff --git a/docs/payment-methods/GOOGLE_PAY.md b/docs/payment-methods/GOOGLE_PAY.md index 1fc5b3fdf2..af91d24647 100644 --- a/docs/payment-methods/GOOGLE_PAY.md +++ b/docs/payment-methods/GOOGLE_PAY.md @@ -86,8 +86,8 @@ CheckoutConfiguration( | `setTotalPriceStatus` | Sets the status of the total price used. | | `setGooglePayButtonStyling` | Allows to configure the styling of the Google Pay button. | -## Migrating to 5.8.0+ -It is not necessary to migrate, but 5.8.0 introduced a simplified integration for Google Pay. This new integration among others gets rid of the deprecated `onActivityResult` and includes the Google Pay button. Follow the steps below to migrate from previous 5.x.x versions to 5.8.0: +## Migrating to 5.9.0+ +It is not necessary to migrate, but 5.9.0 introduced a simplified integration for Google Pay. This new integration among others gets rid of the deprecated `onActivityResult` and includes the Google Pay button. Follow the steps below to migrate from previous 5.x.x versions to 5.9.0: ### 1. Remove deprecated Activity Result code diff --git a/example-app/build.gradle b/example-app/build.gradle index c9e78aa51d..4b6c58adec 100644 --- a/example-app/build.gradle +++ b/example-app/build.gradle @@ -71,8 +71,8 @@ dependencies { // Checkout implementation project(':drop-in') implementation project(':components-compose') -// implementation "com.adyen.checkout:drop-in:5.8.0" -// implementation "com.adyen.checkout:components-compose:5.8.0" +// implementation "com.adyen.checkout:drop-in:5.9.0" +// implementation "com.adyen.checkout:components-compose:5.9.0" // Dependencies implementation libs.bundles.kotlin.coroutines diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6ca85b775..8e2de8ac51 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ min-sdk = "21" # App version, only used for example app, no need to increment version-code = "1" # The version-name format is "major.minor.patch(-(alpha|beta|rc)[0-9]{2}){0,1}" (e.g. 3.0.0, 3.1.1-alpha04 or 3.1.4-rc01 etc). -version-name = "5.8.0" +version-name = "5.9.0" # Build script android-gradle-plugin = "8.7.3" From d0e480d3a875429c4f1a09c05e9d32d0ae9cffb8 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 Jan 2025 14:10:15 +0100 Subject: [PATCH 95/95] Update release notes --- RELEASE_NOTES.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 730bcc27ab..5f3d517d41 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,24 +9,30 @@ [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) ## New -- Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. -- For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. +- Simplified integration for Google Pay: + - The Component now includes the Google Pay button, and you no longer have to display the button with your own configuration. + - The Component now does the availability check on initialization. + - The deprecated `onActivityResult` is no longer needed. -## Fixed + See the [migration guide](docs/payment-methods/GOOGLE_PAY.md#migrating-to-590) to learn about the changes you have to make to your integration to support Google Pay on this version. -## Improved +- Drop-in now shows a toolbar on more intermediary screens to let shoppers navigate back easily. ## Changed -- For 3DS2 native flow, cancellations by shopper trigger `onAdditionalDetails()` event. You can make a `/payments/details` call to gain more insight into the transaction. - - If you already handle other 3DS2 errors by making a `/payments/details` call, then no changes are required and the specific handling for `Cancelled3DS2Exception` can be removed. - - If not yet implemented, you should update your systems to handle shopper cancellations using the new flow. +- For [native 3D Secure 2](https://docs.adyen.com/online-payments/3d-secure/native-3ds2/?platform=Android&integration=Drop-in&version=latest), when a shopper cancels the payment during the payment flow, the `onAdditionalDetails()` event is now triggered. What this means for your integration depends on whether you already make a `/payments/details` call to handle 3D Secure 2 errors: + - If yes, you do not need to make any changes to your integration. You can remove the `Cancelled3DS2Exception` handler. + - If not, update your integration to make a `/payments/details` request to get the details of the canceled transaction. - Dependency versions: - | Name | Version | - |--------------------------------------------------------------------------------------------------------|-------------------------------| - | | | + | Name | Version | + |-----------------------------------------------------------------------------------------------------------------------------------------|----------------| + | [Android Gradle Plugin](https://developer.android.com/build/releases/past-releases/agp-8-7-0-release-notes#android-gradle-plugin-8.7.3) | **8.7.3** | + | [AndroidX Compose BoM](https://developer.android.com/develop/ui/compose/bom/bom-mapping) | **2024.12.01** | + | [AndroidX ConstraintLayout](https://developer.android.com/jetpack/androidx/releases/constraintlayout#constraintlayout-2.2.0) | **2.2.0** | + | [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.7) | **2.8.7** | + | [AndroidX Lifecycle ViewModel Compose](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.7) | **2.8.7** | + | [KotlinX Coroutines Play Services](https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.9.0) | **1.9.0** | ## Deprecated -- `Cancelled3DS2Exception` is now deprecated. Shopper cancellation of the 3DS2 native flow triggers the `onAdditionalDetails()` event, enabling a `/payments/details` call for transaction insights. - The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. | Previous | Now | |-----------------------------------------------------------|------------------------------------------------------------------| @@ -35,4 +41,4 @@ | `cash_app_pay_waiting_text` | `checkout_processing_payment` | ## Repository Maintenance -- We are changing the default branch of our SDK repository from `develop` to `main`. If you are using our SDK repository and working with the `develop` branch, we recommend switching to the `main` branch. The `develop` branch will be removed within the next few weeks. +- We are changing the default branch of our SDK repository from `develop` to `main`. If you are using our SDK repository and are working with the `develop` branch, we recommend switching to the `main` branch. The `develop` branch will be removed within the next few weeks.