diff --git a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/DefaultPayByBankUSDelegate.kt b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/DefaultPayByBankUSDelegate.kt index a39ad59db1..de55957c64 100644 --- a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/DefaultPayByBankUSDelegate.kt +++ b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/DefaultPayByBankUSDelegate.kt @@ -26,10 +26,12 @@ import com.adyen.checkout.paybybankus.R import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSBrandLogo import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +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.PaymentComponentUIEvent import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem.LogoItem import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem.TextItem @@ -45,7 +47,7 @@ internal class DefaultPayByBankUSDelegate( override val componentParams: ButtonComponentParams, private val analyticsManager: AnalyticsManager, private val submitHandler: SubmitHandler, -) : PayByBankUSDelegate { +) : PayByBankUSDelegate, ButtonDelegate, UIStateDelegate { private val _outputDataFlow = MutableStateFlow(createOutputData()) override val outputDataFlow: Flow = _outputDataFlow diff --git a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/PayByBankUSDelegate.kt b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/PayByBankUSDelegate.kt index b6cc70c191..fb1a78bfbd 100644 --- a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/PayByBankUSDelegate.kt +++ b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/PayByBankUSDelegate.kt @@ -11,16 +11,12 @@ package com.adyen.checkout.paybybankus.internal import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.paybybankus.PayByBankUSComponentState import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData -import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate -import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow internal interface PayByBankUSDelegate : PaymentComponentDelegate, - ViewProvidingDelegate, - ButtonDelegate, - UIStateDelegate { + ViewProvidingDelegate { val outputData: PayByBankUSOutputData diff --git a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/StoredPayByBankUSDelegate.kt b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/StoredPayByBankUSDelegate.kt index b0356e612d..3030357d8b 100644 --- a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/StoredPayByBankUSDelegate.kt +++ b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/StoredPayByBankUSDelegate.kt @@ -18,17 +18,19 @@ import com.adyen.checkout.components.core.internal.PaymentObserverRepository import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.ui.model.ButtonComponentParams +import com.adyen.checkout.components.core.internal.util.bufferedChannel import com.adyen.checkout.components.core.paymentmethod.PayByBankUSPaymentMethod +import com.adyen.checkout.core.AdyenLogLevel +import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.paybybankus.PayByBankUSComponentState import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData -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.PaymentComponentUIEvent -import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState -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.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow @Suppress("TooManyFunctions") internal class StoredPayByBankUSDelegate( @@ -37,7 +39,6 @@ internal class StoredPayByBankUSDelegate( private val order: OrderRequest?, override val componentParams: ButtonComponentParams, private val analyticsManager: AnalyticsManager, - private val submitHandler: SubmitHandler, ) : PayByBankUSDelegate { private val _outputDataFlow = MutableStateFlow(createOutputData()) @@ -52,13 +53,35 @@ internal class StoredPayByBankUSDelegate( private val _viewFlow: MutableStateFlow = MutableStateFlow(null) override val viewFlow: Flow = _viewFlow - override val submitFlow: Flow = submitHandler.submitFlow - - override val uiStateFlow: Flow = submitHandler.uiStateFlow - override val uiEventFlow: Flow = submitHandler.uiEventFlow + private val submitChannel = bufferedChannel() + override val submitFlow: Flow = submitChannel.receiveAsFlow() override fun initialize(coroutineScope: CoroutineScope) { - submitHandler.initialize(coroutineScope, componentStateFlow) + initializeAnalytics(coroutineScope) + + componentStateFlow.onEach { + onState(it) + }.launchIn(coroutineScope) + } + + private fun initializeAnalytics(coroutineScope: CoroutineScope) { + adyenLog(AdyenLogLevel.VERBOSE) { "initializeAnalytics" } + analyticsManager.initialize(this, coroutineScope) + + val event = GenericEvents.rendered( + component = storedPaymentMethod.type.orEmpty(), + isStoredPaymentMethod = true, + ) + analyticsManager.trackEvent(event) + } + + private fun onState(achDirectDebitComponentState: PayByBankUSComponentState) { + if (achDirectDebitComponentState.isValid) { + val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + + submitChannel.trySend(achDirectDebitComponentState) + } } override fun getPaymentMethodType(): String { @@ -109,20 +132,8 @@ internal class StoredPayByBankUSDelegate( ) } - override fun onSubmit() { - val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - - val state = _componentStateFlow.value - submitHandler.onSubmit(state) - } - - override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType - - override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible - override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - submitHandler.setInteractionBlocked(isInteractionBlocked) + // no ops } override fun onCleared() { diff --git a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt index 9b4c99aad4..c3dae00c7d 100644 --- a/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt +++ b/paybybank-us/src/main/java/com/adyen/checkout/paybybankus/internal/provider/PayByBankUSComponentProvider.kt @@ -212,7 +212,6 @@ constructor( storedPaymentMethod = storedPaymentMethod, order = order, analyticsManager = analyticsManager, - submitHandler = SubmitHandler(savedStateHandle), ) val genericActionDelegate = @@ -405,7 +404,6 @@ constructor( storedPaymentMethod = storedPaymentMethod, order = checkoutSession.order, analyticsManager = analyticsManager, - submitHandler = SubmitHandler(savedStateHandle), ) val genericActionDelegate = diff --git a/paybybank-us/src/test/java/com/adyen/checkout/paybybankus/internal/ui/StoredPayByBankUSDelegateTest.kt b/paybybank-us/src/test/java/com/adyen/checkout/paybybankus/internal/ui/StoredPayByBankUSDelegateTest.kt new file mode 100644 index 0000000000..8dff22bb61 --- /dev/null +++ b/paybybank-us/src/test/java/com/adyen/checkout/paybybankus/internal/ui/StoredPayByBankUSDelegateTest.kt @@ -0,0 +1,148 @@ +/* + * 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 ozgur on 28/11/2024. + */ + +package com.adyen.checkout.paybybankus.internal.ui + +import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.CheckoutConfiguration +import com.adyen.checkout.components.core.OrderRequest +import com.adyen.checkout.components.core.StoredPaymentMethod +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +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.ButtonComponentParamsMapper +import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper +import com.adyen.checkout.core.Environment +import com.adyen.checkout.paybybankus.PayByBankUSConfiguration +import com.adyen.checkout.paybybankus.getPayByBankUSConfiguration +import com.adyen.checkout.paybybankus.internal.PayByBankUSDelegate +import com.adyen.checkout.paybybankus.internal.StoredPayByBankUSDelegate +import com.adyen.checkout.paybybankus.payByBankUS +import com.adyen.checkout.test.LoggingExtension +import com.adyen.checkout.test.extensions.test +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.junit.jupiter.MockitoExtension +import java.util.Locale + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(MockitoExtension::class, LoggingExtension::class) +class StoredPayByBankUSDelegateTest { + + private lateinit var analyticsManager: TestAnalyticsManager + private lateinit var delegate: PayByBankUSDelegate + + @BeforeEach + fun beforeEach() { + analyticsManager = TestAnalyticsManager() + delegate = createPayByBankUSDelegate() + } + + @Test + fun `when delegate is initialized, then state is valid`() = runTest { + val testFlow = delegate.componentStateFlow.test(testScheduler) + with(testFlow.latestValue) { + assertTrue(isInputValid) + assertTrue(isReady) + assertTrue(isValid) + } + } + + @Test + fun `when delegate is initialized, then submit handler onSubmit is called`() = runTest { + val testFlow = delegate.submitFlow.test(testScheduler) + + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + assertEquals(delegate.componentStateFlow.first(), testFlow.latestValue) + } + + @Nested + inner class AnalyticsTest { + + @Test + fun `when delegate is initialized then analytics manager is initialized`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + analyticsManager.assertIsInitialized() + } + + @Test + fun `when delegate is initialized, then render event is tracked`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val expectedEvent = GenericEvents.rendered( + component = TEST_PAYMENT_METHOD_TYPE, + isStoredPaymentMethod = true, + ) + analyticsManager.assertHasEventEquals(expectedEvent) + } + + @Test + fun `when delegeate is initialized, the component state is submitted, then submit event is tracked`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } + + @Test + fun `when delegate is cleared then analytics manager is cleared`() { + delegate.onCleared() + + analyticsManager.assertIsCleared() + } + } + + private fun createPayByBankUSDelegate( + configuration: CheckoutConfiguration = createCheckoutConfiguration(), + ) = StoredPayByBankUSDelegate( + observerRepository = PaymentObserverRepository(), + componentParams = ButtonComponentParamsMapper(CommonComponentParamsMapper()).mapToParams( + checkoutConfiguration = configuration, + deviceLocale = Locale.US, + dropInOverrideParams = null, + componentSessionParams = null, + componentConfiguration = configuration.getPayByBankUSConfiguration(), + ), + storedPaymentMethod = StoredPaymentMethod( + id = STORED_ID, + type = TEST_PAYMENT_METHOD_TYPE, + ), + order = TEST_ORDER, + analyticsManager = analyticsManager, + ) + + private fun createCheckoutConfiguration( + amount: Amount? = null, + configuration: PayByBankUSConfiguration.Builder.() -> Unit = {} + ) = CheckoutConfiguration( + shopperLocale = Locale.US, + environment = Environment.TEST, + clientKey = TEST_CLIENT_KEY, + amount = amount, + ) { + payByBankUS(configuration) + } + + companion object { + private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" + private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") + private const val STORED_ID = "Stored_id" + private const val TEST_PAYMENT_METHOD_TYPE = "TEST_PAYMENT_METHOD_TYPE" + } +}