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 @@
+
+
+
+
+
+
+
+
+
From 7014a6de9cf5a08aa3f4255eb01fa2a76b0b4784 Mon Sep 17 00:00:00 2001
From: Oscar Spruit
Date: Fri, 11 Oct 2024 14:37:22 +0200
Subject: [PATCH 37/95] Add kdocs to public api
COAND-942
---
.../com/adyen/checkout/googlepay/GooglePayButtonStyling.kt | 7 +++++++
.../com/adyen/checkout/googlepay/GooglePayConfiguration.kt | 5 +++++
2 files changed, 12 insertions(+)
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 79c5bdd5a4..fe2ac968b3 100644
--- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt
+++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt
@@ -13,6 +13,13 @@ import androidx.annotation.Dimension
import com.google.android.gms.wallet.button.ButtonConstants
import kotlinx.parcelize.Parcelize
+/**
+ * Object to style the Google Pay button. Check [the Google docs](https://developers.google.com/pay/api/android/guides/resources/pay-button-api) for more details.
+ *
+ * @param buttonTheme Affects the color scheme of the button.
+ * @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.
+ */
@Parcelize
data class GooglePayButtonStyling(
val buttonTheme: GooglePayButtonTheme? = null,
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 e0bbef6a2a..09a5e5a4da 100644
--- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt
+++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt
@@ -385,6 +385,11 @@ class GooglePayConfiguration private constructor(
return super.setAmount(amount)
}
+ /**
+ * Set a [GooglePayButtonStyling] object for customization of the Google Pay button.
+ *
+ * @param googlePayButtonStyling The customization object.
+ */
fun setGooglePayButtonStyling(googlePayButtonStyling: GooglePayButtonStyling): Builder {
this.googlePayButtonStyling = googlePayButtonStyling
return this
From b1fd2768a9da6f28531b3cb62eded546e08cbc12 Mon Sep 17 00:00:00 2001
From: Oscar Spruit
Date: Fri, 11 Oct 2024 14:37:36 +0200
Subject: [PATCH 38/95] Dump public api
COAND-942
---
example-app/src/main/res/values/styles.xml | 2 +-
googlepay/api/googlepay.api | 56 ++++++++++++++++++-
.../googlepay/GooglePayButtonStyling.kt | 1 +
.../internal/ui/GooglePayButtonView.kt | 1 +
4 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/example-app/src/main/res/values/styles.xml b/example-app/src/main/res/values/styles.xml
index 41c372fcf1..acbb8ba49a 100644
--- a/example-app/src/main/res/values/styles.xml
+++ b/example-app/src/main/res/values/styles.xml
@@ -39,7 +39,7 @@
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
-
-
-
-
diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt
similarity index 61%
rename from cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt
rename to ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt
index 5bb4d79566..521649ee01 100644
--- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt
+++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt
@@ -1,4 +1,12 @@
-package com.adyen.checkout.cashapppay.internal.ui.view
+/*
+ * 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 16/10/2024.
+ */
+
+package com.adyen.checkout.ui.core.internal.ui.view
import android.content.Context
import android.util.AttributeSet
@@ -6,27 +14,28 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
-import com.adyen.checkout.cashapppay.R
-import com.adyen.checkout.cashapppay.databinding.CashAppPayWaitingViewBinding
+import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.ComponentDelegate
+import com.adyen.checkout.ui.core.R
+import com.adyen.checkout.ui.core.databinding.ProcessingPaymentViewBinding
import com.adyen.checkout.ui.core.internal.ui.ComponentView
import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle
import kotlinx.coroutines.CoroutineScope
-import com.adyen.checkout.ui.core.R as UICoreR
-internal class CashAppPayWaitingView @JvmOverloads constructor(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ProcessingPaymentView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr), ComponentView {
- private val binding = CashAppPayWaitingViewBinding.inflate(LayoutInflater.from(context), this)
+ private val binding = ProcessingPaymentViewBinding.inflate(LayoutInflater.from(context), this)
init {
orientation = HORIZONTAL
gravity = Gravity.CENTER
- val padding = resources.getDimension(UICoreR.dimen.standard_margin).toInt()
+ val padding = resources.getDimension(R.dimen.standard_margin).toInt()
setPadding(padding, padding, padding, padding)
}
@@ -36,8 +45,8 @@ internal class CashAppPayWaitingView @JvmOverloads constructor(
private fun initLocalizedStrings(localizedContext: Context) {
binding.textViewPaymentInProgressDescription.setLocalizedTextFromStyle(
- R.style.AdyenCheckout_CashAppPay_WaitingDescriptionTextView,
- localizedContext
+ R.style.AdyenCheckout_ProcessingPaymentView_WaitingDescriptionTextView,
+ localizedContext,
)
}
diff --git a/cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml b/ui-core/src/main/res/layout/processing_payment_view.xml
similarity index 80%
rename from cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml
rename to ui-core/src/main/res/layout/processing_payment_view.xml
index d002b61f8b..ece00fae24 100644
--- a/cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml
+++ b/ui-core/src/main/res/layout/processing_payment_view.xml
@@ -9,12 +9,12 @@
diff --git a/ui-core/src/main/res/template/values/strings.xml.tt b/ui-core/src/main/res/template/values/strings.xml.tt
index 518685147b..676d8239d5 100644
--- a/ui-core/src/main/res/template/values/strings.xml.tt
+++ b/ui-core/src/main/res/template/values/strings.xml.tt
@@ -55,4 +55,6 @@
%%address.enterManually%%
%%address.lookup.submit%%
%%address.lookup.item.validationFailureMessage.empty%%
+
+ %%paypal.processingPayment%%
diff --git a/ui-core/src/main/res/values/strings.xml b/ui-core/src/main/res/values/strings.xml
index f4d78d9e87..6214b33801 100644
--- a/ui-core/src/main/res/values/strings.xml
+++ b/ui-core/src/main/res/values/strings.xml
@@ -55,4 +55,6 @@
Enter address manually
Use this address
Address required
+
+ Processing payment…
diff --git a/ui-core/src/main/res/values/styles.xml b/ui-core/src/main/res/values/styles.xml
index fe3053e36e..5730e1d9ef 100644
--- a/ui-core/src/main/res/values/styles.xml
+++ b/ui-core/src/main/res/values/styles.xml
@@ -419,4 +419,19 @@
- someColor3
-->
+
+
+
+
+
+
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