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 639a297589..ef655a1c0e 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 @@ -34,6 +34,7 @@ import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.internal.PaymentComponentEvent 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.ErrorEvent import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.util.bufferedChannel import com.adyen.checkout.components.core.paymentmethod.CashAppPayPaymentMethod @@ -284,6 +285,7 @@ constructor( } is CashAppPayState.CashAppPayExceptionState -> { + trackThirdPartyErrorEvent() exceptionChannel.trySend( ComponentException("Cash App Pay has encountered an error", newState.exception), ) @@ -311,6 +313,14 @@ constructor( ) } + private fun trackThirdPartyErrorEvent() { + val event = GenericEvents.error( + component = getPaymentMethodType(), + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager.trackEvent(event) + } + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType && componentParams.showStorePaymentField 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 f009258f17..43296b23f5 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 @@ -35,6 +35,7 @@ import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentData import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.PaymentObserverRepository +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 @@ -532,6 +533,20 @@ internal class DefaultCashAppPayDelegateTest( analyticsManager.assertLastEventNotEquals(expectedEvent) } + @Test + fun `when state is exception, then an error is tracked`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val exception = RuntimeException("Stub!") + + delegate.cashAppPayStateDidChange(CashAppPayState.CashAppPayExceptionState(exception)) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = ErrorEvent.THIRD_PARTY + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) 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 af2b87eb71..cf9ceb154d 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 @@ -19,6 +19,7 @@ import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.internal.PaymentComponentEvent 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.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 @@ -149,6 +150,7 @@ internal class DefaultGooglePayDelegate( when (resultCode) { Activity.RESULT_OK -> { if (data == null) { + trackThirdPartyErrorEvent() exceptionChannel.trySend(ComponentException("Result data is null")) return } @@ -161,6 +163,8 @@ internal class DefaultGooglePayDelegate( } AutoResolveHelper.RESULT_ERROR -> { + trackThirdPartyErrorEvent() + val status = AutoResolveHelper.getStatusFromIntent(data) val statusMessage: String = status?.let { ": ${it.statusMessage}" }.orEmpty() exceptionChannel.trySend(ComponentException("GooglePay returned an error$statusMessage")) @@ -170,6 +174,14 @@ internal class DefaultGooglePayDelegate( } } + private fun trackThirdPartyErrorEvent() { + val event = GenericEvents.error( + component = getPaymentMethodType(), + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager.trackEvent(event) + } + override fun getGooglePayButtonParameters(): GooglePayButtonParameters { val allowedPaymentMethodsList = GooglePayUtils.getAllowedPaymentMethods(componentParams) val allowedPaymentMethods = ModelUtils.serializeOptList( 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 b44f4b74be..5c26c122da 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 @@ -8,6 +8,7 @@ package com.adyen.checkout.googlepay.internal.ui +import android.app.Activity import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration @@ -15,6 +16,7 @@ import com.adyen.checkout.components.core.Configuration import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.PaymentObserverRepository +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 @@ -185,6 +187,17 @@ internal class DefaultGooglePayDelegateTest { analyticsManager.assertIsCleared() } + + @Test + fun `when activity result is OK and data is null, then error event is tracked`() { + delegate.handleActivityResult(Activity.RESULT_OK, data = null) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } } private fun createCheckoutConfiguration( 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 4bfceb628b..a23a0fe3e5 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 @@ -23,6 +23,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.data.api.StatusRepository import com.adyen.checkout.components.core.internal.data.model.StatusResponse @@ -163,10 +164,12 @@ internal class DefaultTwintActionDelegate( } TwintPayResult.TW_B_ERROR -> { + trackThirdPartyErrorEvent() onError(ComponentException("Twint encountered an error.")) } TwintPayResult.TW_B_APP_NOT_INSTALLED -> { + trackThirdPartyErrorEvent() onError(ComponentException("Twint app not installed.")) } } @@ -220,6 +223,14 @@ internal class DefaultTwintActionDelegate( ) } + private fun trackThirdPartyErrorEvent() { + val event = GenericEvents.error( + component = action?.paymentMethodType.orEmpty(), + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager?.trackEvent(event) + } + override fun onError(e: CheckoutException) { emitError(e) } diff --git a/twint-action/src/test/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegateTest.kt b/twint-action/src/test/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegateTest.kt index f70203e4a8..560c0db394 100644 --- a/twint-action/src/test/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegateTest.kt +++ b/twint-action/src/test/java/com/adyen/checkout/twint/action/internal/ui/DefaultTwintActionDelegateTest.kt @@ -21,6 +21,7 @@ import com.adyen.checkout.components.core.action.TwintSdkData import com.adyen.checkout.components.core.action.WeChatPaySdkData 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.data.api.TestStatusRepository @@ -287,6 +288,32 @@ internal class DefaultTwintActionDelegateTest { ) analyticsManager.assertLastEventEquals(expectedEvent) } + + @ParameterizedTest + @MethodSource( + "com.adyen.checkout.twint.action.internal.ui.DefaultTwintActionDelegateTest#handleTwintResultErrorEvents", + ) + fun `when handling twint result, then event is tracked`( + result: TwintPayResult, + errorEvent: ErrorEvent + ) = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val action = SdkAction( + paymentMethodType = TEST_PAYMENT_METHOD_TYPE, + type = TEST_ACTION_TYPE, + paymentData = TEST_PAYMENT_DATA, + sdkData = TwintSdkData("token", false), + ) + delegate.handleAction(action, Activity()) + + delegate.handleTwintResult(result) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = errorEvent, + ) + analyticsManager.assertLastEventEquals(expectedEvent) + } } private fun createDelegate( @@ -347,6 +374,19 @@ internal class DefaultTwintActionDelegateTest { TwintTestResult.Error("Twint app not installed."), ), ) + + @JvmStatic + fun handleTwintResultErrorEvents() = listOf( + // result, errorEvent + arguments( + TwintPayResult.TW_B_ERROR, + ErrorEvent.THIRD_PARTY, + ), + arguments( + TwintPayResult.TW_B_APP_NOT_INSTALLED, + ErrorEvent.THIRD_PARTY, + ), + ) } sealed class TwintTestResult { diff --git a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegate.kt b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegate.kt index 6353ab54da..91ddf717a0 100644 --- a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegate.kt +++ b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegate.kt @@ -23,6 +23,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.ui.model.GenericComponentParams import com.adyen.checkout.components.core.internal.util.bufferedChannel @@ -169,6 +170,7 @@ constructor( val isWeChatNotInitiated = !initiateWeChatPayRedirect(sdkData, activityName) if (isWeChatNotInitiated) { + trackThirdPartyErrorEvent() emitError(ComponentException("Failed to initialize WeChat app")) } } @@ -187,6 +189,14 @@ constructor( ) } + private fun trackThirdPartyErrorEvent() { + val event = GenericEvents.error( + component = action?.paymentMethodType.orEmpty(), + event = ErrorEvent.THIRD_PARTY, + ) + analyticsManager?.trackEvent(event) + } + override fun onError(e: CheckoutException) { emitError(e) } diff --git a/wechatpay/src/test/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegateTest.kt b/wechatpay/src/test/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegateTest.kt index c717529aa5..8d94abc1b7 100644 --- a/wechatpay/src/test/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegateTest.kt +++ b/wechatpay/src/test/java/com/adyen/checkout/wechatpay/internal/ui/DefaultWeChatDelegateTest.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.components.core.action.SdkAction import com.adyen.checkout.components.core.action.WeChatPaySdkData 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 @@ -170,6 +171,25 @@ internal class DefaultWeChatDelegateTest( component = TEST_PAYMENT_METHOD_TYPE, subType = TEST_ACTION_TYPE, ) + analyticsManager.assertHasEventEquals(expectedEvent) + } + + @Test + fun `when handleAction is called and sending a request to WeChat fails, then an error is tracked`() { + whenever(iwxApi.sendReq(any())) doReturn false + val action = SdkAction( + paymentMethodType = TEST_PAYMENT_METHOD_TYPE, + type = TEST_ACTION_TYPE, + paymentData = TEST_PAYMENT_DATA, + sdkData = WeChatPaySdkData(), + ) + + delegate.handleAction(action, Activity()) + + val expectedEvent = GenericEvents.error( + component = TEST_PAYMENT_METHOD_TYPE, + event = ErrorEvent.THIRD_PARTY, + ) analyticsManager.assertLastEventEquals(expectedEvent) } }