From 3a5017a65a720ed061462216b6ca6d90614a9a8d Mon Sep 17 00:00:00 2001 From: Timothy Chow Date: Tue, 19 Nov 2024 16:02:41 -0600 Subject: [PATCH] Add url parameter for passing the appSwitchUrl for Venmo analytic events --- .../api/core/AnalyticsClient.kt | 5 +- .../api/core/AnalyticsEvent.kt | 3 +- .../api/core/AnalyticsEventParams.kt | 3 +- .../api/core/AnalyticsClientUnitTest.kt | 7 +- .../api/venmo/VenmoClient.kt | 2 +- .../api/venmo/VenmoLauncher.kt | 18 +- .../api/venmo/VenmoClientUnitTest.java | 236 ++++++++++-------- .../api/venmo/VenmoLauncherUnitTest.kt | 36 ++- 8 files changed, 191 insertions(+), 119 deletions(-) diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt index 1660d9b82d..6f5a893139 100644 --- a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt @@ -44,7 +44,8 @@ class AnalyticsClient internal constructor( endTime = analyticsEventParams.endTime, endpoint = analyticsEventParams.endpoint, experiment = analyticsEventParams.experiment, - paymentMethodsDisplayed = analyticsEventParams.paymentMethodsDisplayed + paymentMethodsDisplayed = analyticsEventParams.paymentMethodsDisplayed, + appSwitchUrl = analyticsEventParams.appSwitchUrl ) configurationLoader.loadConfiguration { result -> if (result is ConfigurationLoaderResult.Success) { @@ -240,6 +241,7 @@ class AnalyticsClient internal constructor( .putOpt(FPTI_KEY_MERCHANT_EXPERIMENT, event.experiment) .putOpt(FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED, event.paymentMethodsDisplayed.ifEmpty { null }) + .putOpt(FPTI_KEY_URL, event.appSwitchUrl) return json.toString() } @@ -292,6 +294,7 @@ class AnalyticsClient internal constructor( private const val FPTI_KEY_ENDPOINT = "endpoint" private const val FPTI_KEY_MERCHANT_EXPERIMENT = "experiment" private const val FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED = "payment_methods_displayed" + private const val FPTI_KEY_URL = "url" private const val FPTI_BATCH_KEY_VENMO_INSTALLED = "venmo_installed" private const val FPTI_BATCH_KEY_PAYPAL_INSTALLED = "paypal_installed" diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEvent.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEvent.kt index ba56b2a6ff..f4278b9816 100644 --- a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEvent.kt +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEvent.kt @@ -15,5 +15,6 @@ internal data class AnalyticsEvent( val endTime: Long? = null, val endpoint: String? = null, val experiment: String? = null, - val paymentMethodsDisplayed: List = emptyList() + val paymentMethodsDisplayed: List = emptyList(), + val appSwitchUrl: String? = null ) diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEventParams.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEventParams.kt index a8a9cd4605..fa309fb55b 100644 --- a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEventParams.kt +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEventParams.kt @@ -27,5 +27,6 @@ data class AnalyticsEventParams @JvmOverloads constructor( var endTime: Long? = null, var endpoint: String? = null, val experiment: String? = null, - val paymentMethodsDisplayed: List = emptyList() + val paymentMethodsDisplayed: List = emptyList(), + val appSwitchUrl: String? = null ) diff --git a/BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsClientUnitTest.kt b/BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsClientUnitTest.kt index 50dd581f41..c18742f405 100644 --- a/BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsClientUnitTest.kt +++ b/BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsClientUnitTest.kt @@ -47,6 +47,7 @@ class AnalyticsClientUnitTest { private lateinit var sut: AnalyticsClient private var timestamp: Long = 0 + private val returnUrlScheme = "com.braintreepayments.demo.braintree" @Before @Throws(InvalidArgumentException::class, GeneralSecurityException::class, IOException::class) @@ -74,6 +75,7 @@ class AnalyticsClientUnitTest { every { time.currentTime } returns 123 every { merchantRepository.authorization } returns authorization every { merchantRepository.applicationContext } returns context + every { merchantRepository.returnUrlScheme } returns returnUrlScheme configurationLoader = MockkConfigurationLoaderBuilder() .configuration(configuration) @@ -103,7 +105,7 @@ class AnalyticsClientUnitTest { ) } returns mockk() - sut.sendEvent(eventName) + sut.sendEvent(eventName, AnalyticsEventParams(appSwitchUrl = returnUrlScheme)) val workSpec = workRequestSlot.captured.workSpec assertEquals(AnalyticsWriteToDbWorker::class.java.name, workSpec.workerClassName) @@ -115,7 +117,8 @@ class AnalyticsClientUnitTest { "event_name": "sample-event-name", "t": 123, "is_vault": false, - "tenant_name": "Braintree" + "tenant_name": "Braintree", + "url": "$returnUrlScheme" } """ val actualJSON = workSpec.input.getString(WORK_INPUT_KEY_ANALYTICS_JSON)!! diff --git a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt index 764c51b45e..5aedd7ec4b 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt @@ -335,7 +335,7 @@ class VenmoClient internal constructor( private val analyticsParams: AnalyticsEventParams get() { - val eventParameters = AnalyticsEventParams() + val eventParameters = AnalyticsEventParams(appSwitchUrl = merchantRepository.returnUrlScheme) eventParameters.payPalContextId = payPalContextId eventParameters.linkType = LINK_TYPE eventParameters.isVaultRequest = isVaultRequest diff --git a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt index 6c9cb6ba68..aa380ff07e 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt @@ -8,18 +8,22 @@ import com.braintreepayments.api.BrowserSwitchException import com.braintreepayments.api.BrowserSwitchFinalResult import com.braintreepayments.api.BrowserSwitchStartResult import com.braintreepayments.api.core.AnalyticsClient +import com.braintreepayments.api.core.AnalyticsEventParams import com.braintreepayments.api.core.BraintreeException +import com.braintreepayments.api.core.MerchantRepository /** * Responsible for launching the Venmo app to authenticate users */ class VenmoLauncher internal constructor( private val browserSwitchClient: BrowserSwitchClient, - lazyAnalyticsClient: Lazy + private val merchantRepository: MerchantRepository, + lazyAnalyticsClient: Lazy, ) { constructor() : this( browserSwitchClient = BrowserSwitchClient(), + merchantRepository = MerchantRepository.instance, lazyAnalyticsClient = AnalyticsClient.lazyInstance ) @@ -40,7 +44,10 @@ class VenmoLauncher internal constructor( activity: ComponentActivity, paymentAuthRequest: VenmoPaymentAuthRequest.ReadyToLaunch ): VenmoPendingRequest { - analyticsClient.sendEvent(VenmoAnalytics.APP_SWITCH_STARTED) + analyticsClient.sendEvent( + eventName = VenmoAnalytics.APP_SWITCH_STARTED, + analyticsEventParams = analyticsEventParams + ) try { assertCanPerformBrowserSwitch(activity, paymentAuthRequest.requestParams) } catch (browserSwitchException: BrowserSwitchException) { @@ -80,7 +87,10 @@ class VenmoLauncher internal constructor( pendingRequest: VenmoPendingRequest.Started, intent: Intent ): VenmoPaymentAuthResult { - analyticsClient.sendEvent(VenmoAnalytics.HANDLE_RETURN_STARTED) + analyticsClient.sendEvent( + eventName = VenmoAnalytics.HANDLE_RETURN_STARTED, + analyticsEventParams = analyticsEventParams + ) return when (val browserSwitchResult = browserSwitchClient.completeRequest(intent, pendingRequest.pendingRequestString)) { is BrowserSwitchFinalResult.Success -> VenmoPaymentAuthResult.Success( @@ -114,6 +124,8 @@ class VenmoLauncher internal constructor( browserSwitchClient.assertCanPerformBrowserSwitch(activity, params.browserSwitchOptions) } + private val analyticsEventParams by lazy { AnalyticsEventParams(appSwitchUrl = merchantRepository.returnUrlScheme) } + companion object { private const val VENMO_PACKAGE_NAME = "com.venmo" private fun createBrowserSwitchError(exception: BrowserSwitchException): Exception { diff --git a/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoClientUnitTest.java b/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoClientUnitTest.java index 89f81a09f8..b211f7682d 100644 --- a/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoClientUnitTest.java +++ b/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoClientUnitTest.java @@ -41,6 +41,8 @@ import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; +import java.util.ArrayList; + @RunWith(RobolectricTestRunner.class) public class VenmoClientUnitTest { @@ -63,9 +65,30 @@ public class VenmoClientUnitTest { private final Uri CANCEL_URL = Uri.parse("sample-scheme://x-callback-url/vzero/auth/venmo/cancel"); private final String LINK_TYPE = "universal"; - private final AnalyticsEventParams expectedAnalyticsParams = new AnalyticsEventParams(); - private final AnalyticsEventParams expectedVaultAnalyticsParams = new AnalyticsEventParams(); - + private final String appSwitchUrl = "com.braintreepayments.demo.braintree"; + private final AnalyticsEventParams expectedAnalyticsParams = new AnalyticsEventParams( + null, + LINK_TYPE, + false, + null, + null, + null, + null, + new ArrayList<>(), + appSwitchUrl + ); + private final AnalyticsEventParams expectedVaultAnalyticsParams = new AnalyticsEventParams( + null, + LINK_TYPE, + true, + null, + null, + null, + null, + new ArrayList<>(), + appSwitchUrl + ); + private final MerchantRepository merchantRepository = mock(MerchantRepository.class); @Before @@ -75,16 +98,10 @@ public void beforeEach() throws JSONException { apiClient = mock(ApiClient.class); analyticsParamRepository = mock(AnalyticsParamRepository.class); - expectedAnalyticsParams.setLinkType(LINK_TYPE); - expectedAnalyticsParams.setVaultRequest(false); - - expectedVaultAnalyticsParams.setLinkType(LINK_TYPE); - expectedVaultAnalyticsParams.setVaultRequest(true); - venmoEnabledConfiguration = - Configuration.fromJson(Fixtures.CONFIGURATION_WITH_PAY_WITH_VENMO); + Configuration.fromJson(Fixtures.CONFIGURATION_WITH_PAY_WITH_VENMO); venmoDisabledConfiguration = - Configuration.fromJson(Fixtures.CONFIGURATION_WITHOUT_ACCESS_TOKEN); + Configuration.fromJson(Fixtures.CONFIGURATION_WITHOUT_ACCESS_TOKEN); venmoTokenizeCallback = mock(VenmoTokenizeCallback.class); venmoPaymentAuthRequestCallback = mock(VenmoPaymentAuthRequestCallback.class); sharedPrefsWriter = mock(VenmoSharedPrefsWriter.class); @@ -97,22 +114,23 @@ public void beforeEach() throws JSONException { when(analyticsParamRepository.getSessionId()).thenReturn("session-id"); when(merchantRepository.getIntegrationType()).thenReturn(IntegrationType.CUSTOM); when(merchantRepository.getApplicationContext()).thenReturn(context); + when(merchantRepository.getReturnUrlScheme()).thenReturn(appSwitchUrl); } @Test public void createPaymentAuthRequest_whenCreatePaymentContextFails_collectAddressWithEcdDisabled() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId("sample-venmo-merchant"); @@ -133,22 +151,22 @@ public void createPaymentAuthRequest_whenCreatePaymentContextFails_collectAddres VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.Failure); assertEquals( - "Cannot collect customer data when ECD is disabled. Enable this feature in the Control Panel to collect this data.", - ((VenmoPaymentAuthRequest.Failure) paymentAuthRequest).getError().getMessage()); + "Cannot collect customer data when ECD is disabled. Enable this feature in the Control Panel to collect this data.", + ((VenmoPaymentAuthRequest.Failure) paymentAuthRequest).getError().getMessage()); } @Test public void createPaymentAuthRequest_whenCreatePaymentContextSucceeds_createsVenmoAuthChallenge() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .returnUrlScheme("com.example") - .build(); + .configuration(venmoEnabledConfiguration) + .returnUrlScheme("com.example") + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId("sample-venmo-merchant"); @@ -165,10 +183,10 @@ public void createPaymentAuthRequest_whenCreatePaymentContextSucceeds_createsVen sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); InOrder inOrder = Mockito.inOrder(venmoPaymentAuthRequestCallback, braintreeClient); - inOrder.verify(braintreeClient).sendAnalyticsEvent(VenmoAnalytics.TOKENIZE_STARTED,new AnalyticsEventParams()); + inOrder.verify(braintreeClient).sendAnalyticsEvent(VenmoAnalytics.TOKENIZE_STARTED, new AnalyticsEventParams()); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); inOrder.verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); @@ -196,8 +214,8 @@ public void createPaymentAuthRequest_whenCreatePaymentContextSucceeds_createsVen @Test public void createPaymentAuthRequest_whenConfigurationException_forwardsExceptionToListener() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configurationError(new Exception("Configuration fetching error")) - .build(); + .configurationError(new Exception("Configuration fetching error")) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -214,7 +232,7 @@ public void createPaymentAuthRequest_whenConfigurationException_forwardsExceptio sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.Failure); @@ -225,8 +243,8 @@ public void createPaymentAuthRequest_whenConfigurationException_forwardsExceptio @Test public void createPaymentAuthRequest_whenVenmoNotEnabled_forwardsExceptionToListener() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoDisabledConfiguration) - .build(); + .configuration(venmoDisabledConfiguration) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -243,7 +261,7 @@ public void createPaymentAuthRequest_whenVenmoNotEnabled_forwardsExceptionToList sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.Failure); @@ -254,14 +272,14 @@ public void createPaymentAuthRequest_whenVenmoNotEnabled_forwardsExceptionToList @Test public void createPaymentAuthRequest_whenProfileIdIsNull_appSwitchesWithMerchantId() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -278,7 +296,7 @@ public void createPaymentAuthRequest_whenProfileIdIsNull_appSwitchesWithMerchant sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.ReadyToLaunch); @@ -292,14 +310,14 @@ public void createPaymentAuthRequest_whenProfileIdIsNull_appSwitchesWithMerchant @Test public void createPaymentAuthRequest_whenProfileIdIsSpecified_appSwitchesWithProfileIdAndAccessToken() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId("second-pwv-profile-id"); @@ -316,7 +334,7 @@ public void createPaymentAuthRequest_whenProfileIdIsSpecified_appSwitchesWithPro sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.ReadyToLaunch); @@ -332,8 +350,8 @@ public void createPaymentAuthRequest_whenProfileIdIsSpecified_appSwitchesWithPro @Test public void createPaymentAuthRequest_sendsAnalyticsEvent() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -348,20 +366,20 @@ public void createPaymentAuthRequest_sendsAnalyticsEvent() { merchantRepository ); sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); - verify(braintreeClient).sendAnalyticsEvent(VenmoAnalytics.TOKENIZE_STARTED,new AnalyticsEventParams()); + verify(braintreeClient).sendAnalyticsEvent(VenmoAnalytics.TOKENIZE_STARTED, new AnalyticsEventParams()); } @Test public void createPaymentAuthRequest_whenShouldVaultIsTrue_persistsVenmoVaultTrue() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -383,14 +401,14 @@ public void createPaymentAuthRequest_whenShouldVaultIsTrue_persistsVenmoVaultTru @Test public void createPaymentAuthRequest_whenShouldVaultIsFalse_persistsVenmoVaultFalse() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -412,14 +430,14 @@ public void createPaymentAuthRequest_whenShouldVaultIsFalse_persistsVenmoVaultFa @Test public void createPaymentAuthRequest_withTokenizationKey_persistsVenmoVaultFalse() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextSuccess("venmo-payment-context-id") - .build(); + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -442,13 +460,13 @@ public void createPaymentAuthRequest_withTokenizationKey_persistsVenmoVaultFalse public void createPaymentAuthRequest_whenVenmoApiError_forwardsErrorToListener_andSendsAnalytics() { BraintreeException graphQLError = new BraintreeException("GraphQL error"); BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .sendGraphQLPOSTErrorResponse(graphQLError) - .build(); + .configuration(venmoEnabledConfiguration) + .sendGraphQLPOSTErrorResponse(graphQLError) + .build(); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createPaymentContextError(graphQLError) - .build(); + .createPaymentContextError(graphQLError) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setShouldVault(true); @@ -464,7 +482,7 @@ public void createPaymentAuthRequest_whenVenmoApiError_forwardsErrorToListener_a sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); ArgumentCaptor captor = - ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); + ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class); verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture()); VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof VenmoPaymentAuthRequest.Failure); @@ -475,8 +493,8 @@ public void createPaymentAuthRequest_whenVenmoApiError_forwardsErrorToListener_a @Test public void tokenize_withPaymentContextId_requestFromVenmoApi() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); @@ -492,7 +510,7 @@ public void tokenize_withPaymentContextId_requestFromVenmoApi() { sut.tokenize(paymentAuthResult, venmoTokenizeCallback); verify(venmoApi).createNonceFromPaymentContext(eq("a-resource-id"), - any(VenmoInternalCallback.class)); + any(VenmoInternalCallback.class)); verify(braintreeClient).sendAnalyticsEvent(VenmoAnalytics.APP_SWITCH_SUCCEEDED, expectedAnalyticsParams); } @@ -500,8 +518,8 @@ public void tokenize_withPaymentContextId_requestFromVenmoApi() { @Test public void tokenize_withPaymentAuthResult_whenUserCanceled_returnsCancelAndSendsAnalytics() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(CANCEL_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); @@ -527,17 +545,17 @@ public void tokenize_withPaymentAuthResult_whenUserCanceled_returnsCancelAndSend @Test public void tokenize_onGraphQLPostSuccess_returnsNonceToListener_andSendsAnalytics() - throws JSONException { + throws JSONException { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextSuccess(VenmoAccountNonce.fromJSON( - new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE))) - .build(); + .createNonceFromPaymentContextSuccess(VenmoAccountNonce.fromJSON( + new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE))) + .build(); VenmoClient sut = new VenmoClient( braintreeClient, @@ -566,15 +584,15 @@ public void tokenize_onGraphQLPostSuccess_returnsNonceToListener_andSendsAnalyti public void tokenize_onGraphQLPostFailure_forwardsExceptionToListener_andSendsAnalytics() { BraintreeException graphQLError = new BraintreeException("graphQL error"); BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .sendGraphQLPOSTErrorResponse(graphQLError) - .build(); + .configuration(venmoEnabledConfiguration) + .sendGraphQLPOSTErrorResponse(graphQLError) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextError(graphQLError) - .build(); + .createNonceFromPaymentContextError(graphQLError) + .build(); VenmoClient sut = new VenmoClient( braintreeClient, @@ -599,8 +617,8 @@ public void tokenize_onGraphQLPostFailure_forwardsExceptionToListener_andSendsAn @Test public void tokenize_withPaymentContext_performsVaultRequestIfRequestPersisted() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); @@ -608,8 +626,8 @@ public void tokenize_withPaymentContext_performsVaultRequestIfRequestPersisted() when(nonce.getString()).thenReturn("some-nonce"); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextSuccess(nonce) - .build(); + .createNonceFromPaymentContextSuccess(nonce) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -649,21 +667,21 @@ public void tokenize_postsPaymentMethodNonceOnSuccess() { sut.tokenize(paymentAuthResult, venmoTokenizeCallback); verify(venmoApi).createNonceFromPaymentContext(eq("a-resource-id"), - any(VenmoInternalCallback.class)); + any(VenmoInternalCallback.class)); } @Test public void tokenize_performsVaultRequestIfRequestPersisted() throws JSONException { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(venmoEnabledConfiguration) - .build(); + .configuration(venmoEnabledConfiguration) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextSuccess(VenmoAccountNonce.fromJSON( - new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE))) - .build(); + .createNonceFromPaymentContextSuccess(VenmoAccountNonce.fromJSON( + new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE))) + .build(); VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); request.setProfileId(null); @@ -683,7 +701,7 @@ public void tokenize_performsVaultRequestIfRequestPersisted() throws JSONExcepti sut.tokenize(paymentAuthResult, venmoTokenizeCallback); verify(venmoApi).vaultVenmoAccountNonce(eq("fake-venmo-nonce"), - any(VenmoInternalCallback.class)); + any(VenmoInternalCallback.class)); } @Test @@ -705,21 +723,21 @@ public void tokenize_doesNotPerformRequestIfTokenizationKeyUsed() { sut.tokenize(paymentAuthResult, venmoTokenizeCallback); verify(venmoApi, never()).vaultVenmoAccountNonce(anyString(), - any(VenmoInternalCallback.class)); + any(VenmoInternalCallback.class)); } @Test public void tokenize_withSuccessfulVaultCall_forwardsResultToActivityResultListener_andSendsAnalytics() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .build(); + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL_WITHOUT_RESOURCE_ID); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoAccountNonce venmoAccountNonce = mock(VenmoAccountNonce.class); VenmoApi venmoApi = new MockVenmoApiBuilder() - .vaultVenmoAccountNonceSuccess(venmoAccountNonce) - .build(); + .vaultVenmoAccountNonceSuccess(venmoAccountNonce) + .build(); when(sharedPrefsWriter.getVenmoVaultOption(context)).thenReturn(true); @@ -746,20 +764,20 @@ public void tokenize_withSuccessfulVaultCall_forwardsResultToActivityResultListe @Test public void tokenize_withPaymentContext_withSuccessfulVaultCall_forwardsNonceToCallback_andSendsAnalytics() - throws JSONException { + throws JSONException { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .sendGraphQLPOSTSuccessfulResponse(Fixtures.VENMO_GRAPHQL_GET_PAYMENT_CONTEXT_RESPONSE) - .build(); + .sendGraphQLPOSTSuccessfulResponse(Fixtures.VENMO_GRAPHQL_GET_PAYMENT_CONTEXT_RESPONSE) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoAccountNonce venmoAccountNonce = VenmoAccountNonce.fromJSON( - new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE)); + new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE)); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextSuccess(venmoAccountNonce) - .vaultVenmoAccountNonceSuccess(venmoAccountNonce) - .build(); + .createNonceFromPaymentContextSuccess(venmoAccountNonce) + .vaultVenmoAccountNonceSuccess(venmoAccountNonce) + .build(); when(sharedPrefsWriter.getVenmoVaultOption(context)).thenReturn(true); @@ -793,8 +811,8 @@ public void tokenize_withFailedVaultCall_forwardsErrorToActivityResultListener_a Exception error = new Exception("error"); VenmoApi venmoApi = new MockVenmoApiBuilder() - .vaultVenmoAccountNonceError(error) - .build(); + .vaultVenmoAccountNonceError(error) + .build(); when(sharedPrefsWriter.getVenmoVaultOption(context)).thenReturn(true); @@ -824,21 +842,21 @@ public void tokenize_withFailedVaultCall_forwardsErrorToActivityResultListener_a @Test public void tokenize_withPaymentContext_withFailedVaultCall_forwardsErrorToCallback_andSendsAnalytics() - throws JSONException { + throws JSONException { BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .sendGraphQLPOSTSuccessfulResponse(Fixtures.VENMO_GRAPHQL_GET_PAYMENT_CONTEXT_RESPONSE) - .build(); + .sendGraphQLPOSTSuccessfulResponse(Fixtures.VENMO_GRAPHQL_GET_PAYMENT_CONTEXT_RESPONSE) + .build(); when(browserSwitchResult.getReturnUrl()).thenReturn(SUCCESS_URL); when(merchantRepository.getAuthorization()).thenReturn(clientToken); VenmoAccountNonce venmoAccountNonce = VenmoAccountNonce.fromJSON( - new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE)); + new JSONObject(Fixtures.PAYMENT_METHODS_VENMO_ACCOUNT_RESPONSE)); Exception error = new Exception("error"); VenmoApi venmoApi = new MockVenmoApiBuilder() - .createNonceFromPaymentContextSuccess(venmoAccountNonce) - .vaultVenmoAccountNonceError(error) - .build(); + .createNonceFromPaymentContextSuccess(venmoAccountNonce) + .vaultVenmoAccountNonceError(error) + .build(); when(sharedPrefsWriter.getVenmoVaultOption(context)).thenReturn(true); diff --git a/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt b/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt index f3ac408a9b..b14e483837 100644 --- a/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt +++ b/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt @@ -7,8 +7,12 @@ import com.braintreepayments.api.BrowserSwitchException import com.braintreepayments.api.BrowserSwitchFinalResult import com.braintreepayments.api.BrowserSwitchOptions import com.braintreepayments.api.BrowserSwitchStartResult +import com.braintreepayments.api.core.AnalyticsClient +import com.braintreepayments.api.core.AnalyticsEventParams +import com.braintreepayments.api.core.MerchantRepository import io.mockk.every import io.mockk.mockk +import io.mockk.verify import junit.framework.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertSame @@ -23,6 +27,8 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class VenmoLauncherUnitTest { private val browserSwitchClient: BrowserSwitchClient = mockk(relaxed = true) + private val merchantRepository: MerchantRepository = mockk(relaxed = true) + private val analyticsClient: AnalyticsClient = mockk(relaxed = true) private val activity: ComponentActivity = mockk(relaxed = true) private val paymentAuthRequestParams: VenmoPaymentAuthRequestParams = mockk(relaxed = true) private val intent: Intent = mockk(relaxed = true) @@ -31,10 +37,26 @@ class VenmoLauncherUnitTest { private lateinit var sut: VenmoLauncher + private val appSwitchUrl = "com.braintreepayments.demo.braintree" + @Before fun setup() { every { paymentAuthRequestParams.browserSwitchOptions } returns options - sut = VenmoLauncher(browserSwitchClient, lazy { mockk(relaxed = true) }) + every { merchantRepository.returnUrlScheme } returns appSwitchUrl + + sut = VenmoLauncher(browserSwitchClient, merchantRepository, lazy { analyticsClient }) + } + + @Test + fun `when launch is invoked, app switch started analytics event is sent`() { + sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.APP_SWITCH_STARTED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl) + ) + } } @Test @@ -92,6 +114,18 @@ class VenmoLauncherUnitTest { ) } + @Test + fun `when handleReturnToApp is invoked, app switch started analytics event is sent`() { + sut.handleReturnToApp(VenmoPendingRequest.Started(pendingRequestString), intent) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.HANDLE_RETURN_STARTED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl) + ) + } + } + @Test fun `handleReturnToApp when result exists returns result`() { val browserSwitchFinalResult = mockk()