From 12e3bb0c472d9adda8fdda93d49d8957d1299d3f Mon Sep 17 00:00:00 2001 From: Tim Chow Date: Thu, 21 Nov 2024 11:39:05 -0600 Subject: [PATCH] Add app switch analytics calls for Venmo (#1218) * WIP - Add app switch analytics calls for Venmo * refactoring * fix tests * Add url parameter for passing the appSwitchUrl for Venmo analytic events * Save Venmo URL to VenmoRepository and send value with Venmo analytic events * Venmo Analytics - add additional app switch and handle return events --------- Co-authored-by: Sai --- .../api/core/AnalyticsClient.kt | 9 +- .../api/core/AnalyticsEvent.kt | 3 +- .../api/core/AnalyticsEventParams.kt | 3 +- .../api/core/ConfigurationLoader.kt | 4 +- .../api/core/AnalyticsClientUnitTest.kt | 7 +- .../api/venmo/VenmoAnalytics.kt | 9 +- .../api/venmo/VenmoClient.kt | 5 +- .../api/venmo/VenmoLauncher.kt | 56 +++- .../api/venmo/VenmoRepository.kt | 22 ++ .../api/venmo/VenmoClientUnitTest.java | 306 ++++++++++-------- .../api/venmo/VenmoLauncherUnitTest.kt | 133 +++++++- 11 files changed, 401 insertions(+), 156 deletions(-) create mode 100644 Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoRepository.kt 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 d2f580d3b1..6f5a893139 100644 --- a/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt @@ -26,6 +26,7 @@ class AnalyticsClient internal constructor( private val configurationLoader: ConfigurationLoader = ConfigurationLoader.instance, private val merchantRepository: MerchantRepository = MerchantRepository.instance ) { + private val applicationContext: Context get() = merchantRepository.applicationContext @@ -43,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) { @@ -239,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() } @@ -270,6 +273,9 @@ class AnalyticsClient internal constructor( } companion object { + + val lazyInstance: Lazy = lazy { AnalyticsClient() } + private const val FPTI_ANALYTICS_URL = "https://api-m.paypal.com/v1/tracking/batch/events" private const val FPTI_KEY_PAYPAL_CONTEXT_ID = "paypal_context_id" @@ -288,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/main/java/com/braintreepayments/api/core/ConfigurationLoader.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/core/ConfigurationLoader.kt index 4c28e0f5a4..cbd415cb6a 100644 --- a/BraintreeCore/src/main/java/com/braintreepayments/api/core/ConfigurationLoader.kt +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/core/ConfigurationLoader.kt @@ -15,7 +15,9 @@ internal class ConfigurationLoader( * TODO: AnalyticsClient must be lazy due to the circular dependency between ConfigurationLoader and AnalyticsClient * This should be refactored to remove the circular dependency. */ - lazyAnalyticsClient: Lazy = lazy { AnalyticsClient(httpClient) }, + lazyAnalyticsClient: Lazy = lazy { + AnalyticsClient(httpClient = httpClient) + }, ) { private val analyticsClient: AnalyticsClient by lazyAnalyticsClient 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/VenmoAnalytics.kt b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoAnalytics.kt index 119b12b9a6..19319683c1 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoAnalytics.kt +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoAnalytics.kt @@ -8,7 +8,14 @@ internal object VenmoAnalytics { const val TOKENIZE_SUCCEEDED = "venmo:tokenize:succeeded" const val APP_SWITCH_CANCELED = "venmo:tokenize:app-switch:canceled" - // Additional Detail Events + // Launching App Switch events + const val APP_SWITCH_STARTED = "venmo:tokenize:app-switch:started" const val APP_SWITCH_SUCCEEDED = "venmo:tokenize:app-switch:succeeded" const val APP_SWITCH_FAILED = "venmo:tokenize:app-switch:failed" + + // Handle return events + const val HANDLE_RETURN_STARTED = "venmo:tokenize:handle-return:started" + const val HANDLE_RETURN_SUCCEEDED = "venmo:tokenize:handle-return:succeeded" + const val HANDLE_RETURN_FAILED = "venmo:tokenize:handle-return:failed" + const val HANDLE_RETURN_NO_RESULT = "venmo:tokenize:handle-return:no-result" } 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..a7748e07a7 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt @@ -32,6 +32,7 @@ class VenmoClient internal constructor( private val sharedPrefsWriter: VenmoSharedPrefsWriter = VenmoSharedPrefsWriter(), private val analyticsParamRepository: AnalyticsParamRepository = AnalyticsParamRepository.instance, private val merchantRepository: MerchantRepository = MerchantRepository.instance, + private val venmoRepository: VenmoRepository = VenmoRepository.instance ) { /** * Used for linking events from the client to server side request @@ -183,6 +184,8 @@ class VenmoClient internal constructor( .appendQueryParameter("customerClient", "MOBILE_APP") .build() + venmoRepository.venmoUrl = venmoBaseURL + val browserSwitchOptions = BrowserSwitchOptions() .requestCode(BraintreeRequestCodes.VENMO.code) .url(venmoBaseURL) @@ -335,7 +338,7 @@ class VenmoClient internal constructor( private val analyticsParams: AnalyticsEventParams get() { - val eventParameters = AnalyticsEventParams() + val eventParameters = AnalyticsEventParams(appSwitchUrl = venmoRepository.venmoUrl.toString()) 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 bb9316f1a6..e66dbcb0f5 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoLauncher.kt @@ -7,15 +7,26 @@ import com.braintreepayments.api.BrowserSwitchClient 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 /** * Responsible for launching the Venmo app to authenticate users */ class VenmoLauncher internal constructor( - private val browserSwitchClient: BrowserSwitchClient + private val browserSwitchClient: BrowserSwitchClient, + private val venmoRepository: VenmoRepository, + lazyAnalyticsClient: Lazy, ) { - constructor() : this(BrowserSwitchClient()) + + constructor() : this( + browserSwitchClient = BrowserSwitchClient(), + venmoRepository = VenmoRepository.instance, + lazyAnalyticsClient = AnalyticsClient.lazyInstance + ) + + private val analyticsClient: AnalyticsClient by lazyAnalyticsClient /** * Launches the Venmo authentication flow by switching to the Venmo app or a mobile browser, if @@ -32,9 +43,11 @@ class VenmoLauncher internal constructor( activity: ComponentActivity, paymentAuthRequest: VenmoPaymentAuthRequest.ReadyToLaunch ): VenmoPendingRequest { + analyticsClient.sendEvent(VenmoAnalytics.APP_SWITCH_STARTED, analyticsEventParams) try { assertCanPerformBrowserSwitch(activity, paymentAuthRequest.requestParams) } catch (browserSwitchException: BrowserSwitchException) { + analyticsClient.sendEvent(VenmoAnalytics.APP_SWITCH_FAILED, analyticsEventParams) val manifestInvalidError = createBrowserSwitchError(browserSwitchException) return VenmoPendingRequest.Failure(manifestInvalidError) } @@ -43,8 +56,15 @@ class VenmoLauncher internal constructor( paymentAuthRequest.requestParams.browserSwitchOptions ) return when (request) { - is BrowserSwitchStartResult.Failure -> VenmoPendingRequest.Failure(request.error) - is BrowserSwitchStartResult.Started -> VenmoPendingRequest.Started(request.pendingRequest) + is BrowserSwitchStartResult.Failure -> { + analyticsClient.sendEvent(VenmoAnalytics.APP_SWITCH_FAILED, analyticsEventParams) + VenmoPendingRequest.Failure(request.error) + } + + is BrowserSwitchStartResult.Started -> { + analyticsClient.sendEvent(VenmoAnalytics.APP_SWITCH_SUCCEEDED, analyticsEventParams) + VenmoPendingRequest.Started(request.pendingRequest) + } } } @@ -71,15 +91,23 @@ class VenmoLauncher internal constructor( pendingRequest: VenmoPendingRequest.Started, intent: Intent ): VenmoPaymentAuthResult { + analyticsClient.sendEvent(VenmoAnalytics.HANDLE_RETURN_STARTED, analyticsEventParams) return when (val browserSwitchResult = browserSwitchClient.completeRequest(intent, pendingRequest.pendingRequestString)) { - is BrowserSwitchFinalResult.Success -> VenmoPaymentAuthResult.Success( - browserSwitchResult - ) + is BrowserSwitchFinalResult.Success -> { + analyticsClient.sendEvent(VenmoAnalytics.HANDLE_RETURN_SUCCEEDED, analyticsEventParams) + VenmoPaymentAuthResult.Success(browserSwitchResult) + } - is BrowserSwitchFinalResult.Failure -> VenmoPaymentAuthResult.Failure(browserSwitchResult.error) + is BrowserSwitchFinalResult.Failure -> { + analyticsClient.sendEvent(VenmoAnalytics.HANDLE_RETURN_FAILED, analyticsEventParams) + VenmoPaymentAuthResult.Failure(browserSwitchResult.error) + } - is BrowserSwitchFinalResult.NoResult -> VenmoPaymentAuthResult.NoResult + is BrowserSwitchFinalResult.NoResult -> { + analyticsClient.sendEvent(VenmoAnalytics.HANDLE_RETURN_NO_RESULT, analyticsEventParams) + VenmoPaymentAuthResult.NoResult + } } } @@ -104,14 +132,18 @@ class VenmoLauncher internal constructor( browserSwitchClient.assertCanPerformBrowserSwitch(activity, params.browserSwitchOptions) } + private val analyticsEventParams by lazy { + AnalyticsEventParams(appSwitchUrl = venmoRepository.venmoUrl.toString()) + } + companion object { private const val VENMO_PACKAGE_NAME = "com.venmo" private fun createBrowserSwitchError(exception: BrowserSwitchException): Exception { return BraintreeException( "AndroidManifest.xml is incorrectly configured or another app defines the same " + - "browser switch url as this app. See https://developer.paypal.com/" + - "braintree/docs/guides/client-sdk/setup/android/v4#browser-switch-setup " + - "for the correct configuration: " + exception.message + "browser switch url as this app. See https://developer.paypal.com/" + + "braintree/docs/guides/client-sdk/setup/android/v4#browser-switch-setup " + + "for the correct configuration: " + exception.message ) } } diff --git a/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoRepository.kt b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoRepository.kt new file mode 100644 index 0000000000..2c3f8d7c36 --- /dev/null +++ b/Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoRepository.kt @@ -0,0 +1,22 @@ +package com.braintreepayments.api.venmo + +import android.net.Uri + +/** + * An internal, in memory repository that holds properties specific for the Venmo payment flow. + */ +internal class VenmoRepository { + + /** + * The Venmo URL that is used to load the CCT or app switch into the Venmo payment flow. + */ + var venmoUrl: Uri? = null + + companion object { + + /** + * Singleton instance of the VenmoRepository. + */ + val instance: VenmoRepository by lazy { VenmoRepository() } + } +} 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..5f4ae2d88d 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,10 +65,32 @@ 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 Uri appSwitchUrl = Uri.parse("https://example.com"); + private final AnalyticsEventParams expectedAnalyticsParams = new AnalyticsEventParams( + null, + LINK_TYPE, + false, + null, + null, + null, + null, + new ArrayList<>(), + appSwitchUrl.toString() + ); + private final AnalyticsEventParams expectedVaultAnalyticsParams = new AnalyticsEventParams( + null, + LINK_TYPE, + true, + null, + null, + null, + null, + new ArrayList<>(), + appSwitchUrl.toString() + ); + private final MerchantRepository merchantRepository = mock(MerchantRepository.class); + private final VenmoRepository venmoRepository = mock(VenmoRepository.class); @Before public void beforeEach() throws JSONException { @@ -75,16 +99,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 +115,23 @@ public void beforeEach() throws JSONException { when(analyticsParamRepository.getSessionId()).thenReturn("session-id"); when(merchantRepository.getIntegrationType()).thenReturn(IntegrationType.CUSTOM); when(merchantRepository.getApplicationContext()).thenReturn(context); + when(venmoRepository.getVenmoUrl()).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"); @@ -124,7 +143,8 @@ public void createPaymentAuthRequest_whenCreatePaymentContextFails_collectAddres venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); @@ -133,22 +153,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"); @@ -160,15 +180,16 @@ public void createPaymentAuthRequest_whenCreatePaymentContextSucceeds_createsVen venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +217,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); @@ -209,12 +230,13 @@ public void createPaymentAuthRequest_whenConfigurationException_forwardsExceptio venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +247,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); @@ -238,12 +260,13 @@ public void createPaymentAuthRequest_whenVenmoNotEnabled_forwardsExceptionToList venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +277,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); @@ -273,12 +296,13 @@ public void createPaymentAuthRequest_whenProfileIdIsNull_appSwitchesWithMerchant venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +316,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"); @@ -311,12 +335,13 @@ public void createPaymentAuthRequest_whenProfileIdIsSpecified_appSwitchesWithPro venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +357,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); @@ -345,23 +370,24 @@ public void createPaymentAuthRequest_sendsAnalyticsEvent() { venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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); @@ -373,7 +399,8 @@ public void createPaymentAuthRequest_whenShouldVaultIsTrue_persistsVenmoVaultTru venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); @@ -383,14 +410,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); @@ -402,7 +429,8 @@ public void createPaymentAuthRequest_whenShouldVaultIsFalse_persistsVenmoVaultFa venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); @@ -412,14 +440,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); @@ -431,7 +459,8 @@ public void createPaymentAuthRequest_withTokenizationKey_persistsVenmoVaultFalse venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback); @@ -442,13 +471,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); @@ -459,12 +488,13 @@ public void createPaymentAuthRequest_whenVenmoApiError_forwardsErrorToListener_a venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +505,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); @@ -486,13 +516,14 @@ public void tokenize_withPaymentContextId_requestFromVenmoApi() { venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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 +531,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); @@ -511,7 +542,8 @@ public void tokenize_withPaymentAuthResult_whenUserCanceled_returnsCancelAndSend venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -527,17 +559,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, @@ -545,7 +577,8 @@ public void tokenize_onGraphQLPostSuccess_returnsNonceToListener_andSendsAnalyti venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -566,15 +599,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, @@ -582,7 +615,8 @@ public void tokenize_onGraphQLPostFailure_forwardsExceptionToListener_andSendsAn venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -599,8 +633,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 +642,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); @@ -623,7 +657,8 @@ public void tokenize_withPaymentContext_performsVaultRequestIfRequestPersisted() venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -643,27 +678,28 @@ public void tokenize_postsPaymentMethodNonceOnSuccess() { venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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); @@ -677,13 +713,14 @@ public void tokenize_performsVaultRequestIfRequestPersisted() throws JSONExcepti venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); verify(venmoApi).vaultVenmoAccountNonce(eq("fake-venmo-nonce"), - any(VenmoInternalCallback.class)); + any(VenmoInternalCallback.class)); } @Test @@ -699,27 +736,28 @@ public void tokenize_doesNotPerformRequestIfTokenizationKeyUsed() { venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); 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); @@ -729,7 +767,8 @@ public void tokenize_withSuccessfulVaultCall_forwardsResultToActivityResultListe venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -746,20 +785,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); @@ -769,7 +808,8 @@ public void tokenize_withPaymentContext_withSuccessfulVaultCall_forwardsNonceToC venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -793,8 +833,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); @@ -804,7 +844,8 @@ public void tokenize_withFailedVaultCall_forwardsErrorToActivityResultListener_a venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); @@ -824,21 +865,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); @@ -848,7 +889,8 @@ public void tokenize_withPaymentContext_withFailedVaultCall_forwardsErrorToCallb venmoApi, sharedPrefsWriter, analyticsParamRepository, - merchantRepository + merchantRepository, + venmoRepository ); sut.tokenize(paymentAuthResult, venmoTokenizeCallback); 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 3835be593d..d0224f9a43 100644 --- a/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt +++ b/Venmo/src/test/java/com/braintreepayments/api/venmo/VenmoLauncherUnitTest.kt @@ -1,14 +1,18 @@ package com.braintreepayments.api.venmo import android.content.Intent +import android.net.Uri import androidx.activity.ComponentActivity import com.braintreepayments.api.BrowserSwitchClient 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 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 venmoRepository: VenmoRepository = 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 = Uri.parse("http://example.com") + @Before fun setup() { every { paymentAuthRequestParams.browserSwitchOptions } returns options - sut = VenmoLauncher(browserSwitchClient) + every { venmoRepository.venmoUrl } returns appSwitchUrl + + sut = VenmoLauncher(browserSwitchClient, venmoRepository, 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.toString()) + ) + } } @Test @@ -42,8 +64,7 @@ class VenmoLauncherUnitTest { val startedPendingRequest = BrowserSwitchStartResult.Started(pendingRequestString) every { browserSwitchClient.start(activity, options) } returns startedPendingRequest - val pendingRequest = - sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) + val pendingRequest = sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) assertTrue(pendingRequest is VenmoPendingRequest.Started) assertEquals( @@ -52,6 +73,21 @@ class VenmoLauncherUnitTest { ) } + @Test + fun `when launch is called and Started is returned, app switch succeeded analytics event is sent`() { + val startedPendingRequest = BrowserSwitchStartResult.Started(pendingRequestString) + every { browserSwitchClient.start(activity, options) } returns startedPendingRequest + + sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.APP_SWITCH_SUCCEEDED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + @Test fun `launch on error returns pending request failure`() { every { paymentAuthRequestParams.browserSwitchOptions } returns options @@ -67,7 +103,22 @@ class VenmoLauncherUnitTest { } @Test - @Throws(BrowserSwitchException::class) + fun `when launch is called and Failure is returned, app switch failed analytics event is sent`() { + every { paymentAuthRequestParams.browserSwitchOptions } returns options + every { browserSwitchClient.start(eq(activity), eq(options)) } returns + BrowserSwitchStartResult.Failure(BrowserSwitchException("error")) + + sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.APP_SWITCH_FAILED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + + @Test fun `launch when device cant perform browser switch returns pending request failure`() { every { paymentAuthRequestParams.browserSwitchOptions } returns options val exception = BrowserSwitchException("browser switch error") @@ -92,6 +143,35 @@ class VenmoLauncherUnitTest { ) } + @Test + @Throws(BrowserSwitchException::class) + fun `when launch is called with a manifest error, app switch failed analytics event is sent`() { + every { paymentAuthRequestParams.browserSwitchOptions } returns options + every { browserSwitchClient.assertCanPerformBrowserSwitch(eq(activity), eq(options)) } throws + BrowserSwitchException("browser switch error") + + sut.launch(activity, VenmoPaymentAuthRequest.ReadyToLaunch(paymentAuthRequestParams)) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.APP_SWITCH_FAILED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + + @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.toString()) + ) + } + } + @Test fun `handleReturnToApp when result exists returns result`() { val browserSwitchFinalResult = mockk() @@ -113,6 +193,21 @@ class VenmoLauncherUnitTest { ) } + @Test + fun `when handleReturnToApp is called with Success, handle return succeeded analytics event is sent`() { + every { browserSwitchClient.completeRequest(intent, pendingRequestString) } returns + mockk() + + sut.handleReturnToApp(VenmoPendingRequest.Started(pendingRequestString), intent) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.HANDLE_RETURN_SUCCEEDED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + @Test fun `handleReturnToApp when result does not exist returns null`() { every { @@ -130,6 +225,36 @@ class VenmoLauncherUnitTest { assertTrue(paymentAuthResult is VenmoPaymentAuthResult.NoResult) } + @Test + fun `when handleReturnToApp is called without a result, handle return no result analytics event is sent`() { + every { browserSwitchClient.completeRequest(intent, pendingRequestString) } returns + BrowserSwitchFinalResult.NoResult + + sut.handleReturnToApp(VenmoPendingRequest.Started(pendingRequestString), intent) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.HANDLE_RETURN_NO_RESULT, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + + @Test + fun `when handleReturnToApp is called with a failure, handle return failed analytics event is sent`() { + every { browserSwitchClient.completeRequest(intent, pendingRequestString) } returns + mockk(relaxed = true) + + sut.handleReturnToApp(VenmoPendingRequest.Started(pendingRequestString), intent) + + verify { + analyticsClient.sendEvent( + eventName = VenmoAnalytics.HANDLE_RETURN_FAILED, + analyticsEventParams = AnalyticsEventParams(appSwitchUrl = appSwitchUrl.toString()) + ) + } + } + @Test fun showVenmoInGooglePlayStore_opensVenmoAppStoreURL() { val activity = Mockito.mock(