diff --git a/.github/actions/publish_all_modules/action.yml b/.github/actions/publish_all_modules/action.yml index 71d12b17e0..16b26763f8 100644 --- a/.github/actions/publish_all_modules/action.yml +++ b/.github/actions/publish_all_modules/action.yml @@ -28,6 +28,7 @@ runs: ./gradlew clean \ :AmericanExpress:publishToSonatype \ :BraintreeCore:publishToSonatype \ + :PayPalDataCollector:publishToSonatype \ :BraintreeDataCollector:publishToSonatype \ :Card:publishToSonatype \ :GooglePay:publishToSonatype \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5911a67bc..c81610e1f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,6 +54,18 @@ jobs: uses: ./.github/actions/unit_test_module with: module: BraintreeCore + unit_test_paypal_data_collector: + name: PayPalDataCollector Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + - name: Setup Java 8 + uses: ./.github/actions/setup + - name: Run Unit Tests + uses: ./.github/actions/unit_test_module + with: + module: PayPalDataCollector unit_test_braintree_data_collector: name: BraintreeDataCollector Unit Tests runs-on: ubuntu-latest @@ -204,6 +216,7 @@ jobs: needs: [ unit_test_american_express, unit_test_braintree_core, + unit_test_paypal_data_collector, unit_test_braintree_data_collector, unit_test_card, unit_test_google_pay, diff --git a/.github/workflows/release_snapshot.yml b/.github/workflows/release_snapshot.yml index 66bf810fc3..79f723da61 100644 --- a/.github/workflows/release_snapshot.yml +++ b/.github/workflows/release_snapshot.yml @@ -53,6 +53,18 @@ jobs: uses: ./.github/actions/unit_test_module with: module: BraintreeCore + unit_test_paypal_data_collector: + name: PayPalDataCollector Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + - name: Setup Java 8 + uses: ./.github/actions/setup + - name: Run Unit Tests + uses: ./.github/actions/unit_test_module + with: + module: PayPalDataCollector unit_test_braintree_data_collector: name: BraintreeDataCollector Unit Tests runs-on: ubuntu-latest @@ -203,6 +215,7 @@ jobs: needs: [ unit_test_american_express, unit_test_braintree_core, + unit_test_paypal_data_collector, unit_test_braintree_data_collector, unit_test_card, unit_test_google_pay, diff --git a/BraintreeDataCollector/build.gradle b/BraintreeDataCollector/build.gradle index 752e992999..a9de12c78b 100644 --- a/BraintreeDataCollector/build.gradle +++ b/BraintreeDataCollector/build.gradle @@ -40,10 +40,10 @@ android { dependencies { implementation files('libs/kount-data-collector-3.2.jar') - implementation files('libs/android-magnessdk-5.3.0.jar') implementation deps.annotation api project(':BraintreeCore') + implementation project(':PayPalDataCollector') testImplementation deps.robolectric testImplementation deps.mockitoCore diff --git a/BraintreeDataCollector/src/main/java/com/braintreepayments/api/DataCollector.java b/BraintreeDataCollector/src/main/java/com/braintreepayments/api/DataCollector.java index 9c3815965f..603d242e87 100644 --- a/BraintreeDataCollector/src/main/java/com/braintreepayments/api/DataCollector.java +++ b/BraintreeDataCollector/src/main/java/com/braintreepayments/api/DataCollector.java @@ -25,7 +25,7 @@ public class DataCollector { private final UUIDHelper uuidHelper; public DataCollector(@NonNull BraintreeClient braintreeClient) { - this(braintreeClient, new PayPalDataCollector(), new KountDataCollector(braintreeClient), new UUIDHelper()); + this(braintreeClient, new PayPalDataCollector(braintreeClient), new KountDataCollector(braintreeClient), new UUIDHelper()); } @VisibleForTesting diff --git a/BraintreeDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java b/BraintreeDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java deleted file mode 100644 index ae33ef68cf..0000000000 --- a/BraintreeDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.braintreepayments.api; - -import android.content.Context; -import android.util.Log; - -import androidx.annotation.MainThread; -import androidx.annotation.VisibleForTesting; - -import lib.android.paypal.com.magnessdk.Environment; -import lib.android.paypal.com.magnessdk.InvalidInputException; -import lib.android.paypal.com.magnessdk.MagnesResult; -import lib.android.paypal.com.magnessdk.MagnesSDK; -import lib.android.paypal.com.magnessdk.MagnesSettings; -import lib.android.paypal.com.magnessdk.MagnesSource; - -class PayPalDataCollector { - - private final MagnesSDK magnesSDK; - private final UUIDHelper uuidHelper; - - PayPalDataCollector() { - this(MagnesSDK.getInstance(), new UUIDHelper()); - } - - @VisibleForTesting - PayPalDataCollector(MagnesSDK magnesSDK, UUIDHelper uuidHelper) { - this.magnesSDK = magnesSDK; - this.uuidHelper = uuidHelper; - } - - String getPayPalInstallationGUID(Context context) { - return uuidHelper.getInstallationGUID(context); - } - - /** - * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment - * from their device, PayPal uses the Client Metadata ID to verify that the payment is - * originating from a valid, user-consented device and application. This helps reduce fraud and - * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a - * "future payment") from a mobile device. Pass the result to your server, to include in the - * payment request sent to PayPal. Do not otherwise cache or store this value. - * - * @param context Android Context - * @param configuration the merchant configurationn - * @return clientMetadataId Your server will send this to PayPal - */ - @MainThread - String getClientMetadataId(Context context, Configuration configuration) { - PayPalDataCollectorRequest request = new PayPalDataCollectorRequest() - .setApplicationGuid(getPayPalInstallationGUID(context)); - - return getClientMetadataId(context, request, configuration); - } - - /** - * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment - * from their device, PayPal uses the Client Metadata ID to verify that the payment is - * originating from a valid, user-consented device and application. This helps reduce fraud and - * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a - * "future payment") from a mobile device. Pass the result to your server, to include in the - * payment request sent to PayPal. Do not otherwise cache or store this value. - * - * @param context Android Context. - * @param request configures what data to collect. - * @param configuration the merchant configuration - * @return clientMetadataId Your server will send this to PayPal - */ - @MainThread - String getClientMetadataId(Context context, PayPalDataCollectorRequest request, Configuration configuration) { - if (context == null || context.getApplicationContext() == null) { - return ""; - } - - try { - MagnesSettings.Builder magnesSettingsBuilder = new MagnesSettings.Builder(context.getApplicationContext()) - .setMagnesSource(MagnesSource.BRAINTREE) - .disableBeacon(request.isDisableBeacon()) - .setMagnesEnvironment(getMagnesEnvironment(configuration.getEnvironment())) - .setAppGuid(request.getApplicationGuid()); - - magnesSDK.setUp(magnesSettingsBuilder.build()); - - MagnesResult result = magnesSDK.collectAndSubmit(context.getApplicationContext(), request.getClientMetadataId(), request.getAdditionalData()); - - return result.getPaypalClientMetaDataId(); - } catch (InvalidInputException e) { - // Either clientMetadataId or appGuid exceeds their character limit - Log.e("Exception", "Error fetching client metadata ID. Contact Braintree Support for assistance.", e); - return ""; - } - } - - private Environment getMagnesEnvironment(String environment) { - if ("sandbox".equals(environment)) { - return Environment.SANDBOX; - } - return Environment.LIVE; - } -} diff --git a/BraintreeDataCollector/src/test/java/com/braintreepayments/api/DataCollectorUnitTest.java b/BraintreeDataCollector/src/test/java/com/braintreepayments/api/DataCollectorUnitTest.java index cfef6b4d73..67b3e75435 100644 --- a/BraintreeDataCollector/src/test/java/com/braintreepayments/api/DataCollectorUnitTest.java +++ b/BraintreeDataCollector/src/test/java/com/braintreepayments/api/DataCollectorUnitTest.java @@ -1,5 +1,13 @@ package com.braintreepayments.api; +import static junit.framework.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.content.Context; import com.braintreepayments.api.TestConfigurationBuilder.TestKountConfigurationBuilder; @@ -12,14 +20,6 @@ import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; -import static junit.framework.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(RobolectricTestRunner.class) public class DataCollectorUnitTest { diff --git a/BraintreeDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java b/BraintreeDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java deleted file mode 100644 index b1536436b6..0000000000 --- a/BraintreeDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.braintreepayments.api; - -import android.content.Context; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.robolectric.RobolectricTestRunner; - -import java.util.HashMap; - -import lib.android.paypal.com.magnessdk.Environment; -import lib.android.paypal.com.magnessdk.InvalidInputException; -import lib.android.paypal.com.magnessdk.MagnesResult; -import lib.android.paypal.com.magnessdk.MagnesSDK; -import lib.android.paypal.com.magnessdk.MagnesSettings; -import lib.android.paypal.com.magnessdk.MagnesSource; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyObject; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(RobolectricTestRunner.class) -public class PayPalDataCollectorUnitTest { - - private Context context; - private String sampleInstallationGUID; - private Configuration configuration; - - private MagnesSDK magnesSDK; - private UUIDHelper uuidHelper; - - @Before - public void beforeEach() { - magnesSDK = mock(MagnesSDK.class); - uuidHelper = mock(UUIDHelper.class); - context = mock(Context.class); - when(context.getApplicationContext()).thenReturn(mock(Context.class)); - configuration = mock(Configuration.class); - when(configuration.getEnvironment()).thenReturn("sandbox"); - - // this uuid has no actual meaning; magnes requires a valid guid for tests - sampleInstallationGUID = "0665203b-16e4-4ce2-be98-d7d73ec32e8a"; - } - - @Test - public void getPayPalInstallationGUID_returnsInstallationIdentifier() { - when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - - assertEquals(sampleInstallationGUID, sut.getPayPalInstallationGUID(context)); - } - - @Test - public void getClientMetaDataId_returnsEmptyStringWhenContextIsNull() { - when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - - String result = sut.getClientMetadataId(null, configuration); - assertEquals("", result); - } - - @Test - public void getClientMetadataId_configuresMagnesWithDefaultSettings() throws InvalidInputException { - when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); - when(configuration.getEnvironment()).thenReturn("production"); - - when(magnesSDK.collectAndSubmit(any(Context.class), (String) isNull(), ArgumentMatchers.>isNull())) - .thenReturn(mock(MagnesResult.class)); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - sut.getClientMetadataId(context, configuration); - - ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); - verify(magnesSDK).setUp(captor.capture()); - - MagnesSettings magnesSettings = captor.getValue(); - assertEquals(MagnesSource.BRAINTREE.getVersion(), magnesSettings.getMagnesSource()); - assertFalse(magnesSettings.isDisableBeacon()); - assertEquals(Environment.LIVE, magnesSettings.getEnvironment()); - assertEquals(sampleInstallationGUID, magnesSettings.getAppGuid()); - } - - @Test - public void getClientMetadataId_configuresMagnesWithPayPalDataCollectorRequest() throws InvalidInputException { - when(configuration.getEnvironment()).thenReturn("production"); - String applicationGUID = "07ea6967-1e4a-4aaa-942d-7f13db372b75"; - - HashMap additionalData = new HashMap<>(); - PayPalDataCollectorRequest payPalDataCollectorRequest = new PayPalDataCollectorRequest() - .setApplicationGuid(applicationGUID) - .setClientMetadataId("client-metadata-id") - .setAdditionalData(additionalData) - .setDisableBeacon(false); - - when(magnesSDK.collectAndSubmit(any(Context.class), eq("client-metadata-id"), same(additionalData))) - .thenReturn(mock(MagnesResult.class)); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - sut.getClientMetadataId(context, payPalDataCollectorRequest, configuration); - - ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); - verify(magnesSDK).setUp(captor.capture()); - - MagnesSettings magnesSettings = captor.getValue(); - assertEquals(MagnesSource.BRAINTREE.getVersion(), magnesSettings.getMagnesSource()); - assertFalse(magnesSettings.isDisableBeacon()); - assertEquals(Environment.LIVE, magnesSettings.getEnvironment()); - assertEquals(applicationGUID, magnesSettings.getAppGuid()); - } - - @Test - public void getClientMetadataId_forwardsClientMetadataIdFromMagnesResult() throws InvalidInputException { - when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); - - MagnesResult magnesResult = mock(MagnesResult.class); - when(magnesResult.getPaypalClientMetaDataId()).thenReturn("paypal-clientmetadata-id"); - - when(magnesSDK.collectAndSubmit(any(Context.class), (String) isNull(), ArgumentMatchers.>isNull())) - .thenReturn(magnesResult); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - String result = sut.getClientMetadataId(context, configuration); - - assertEquals("paypal-clientmetadata-id", result); - } - - @Test - public void getClientMetadataId_returnsEmptyStringWhenMagnesInputInvalid() throws InvalidInputException { - when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); - - when(magnesSDK.collectAndSubmit(any(Context.class), (String) isNull(), ArgumentMatchers.>isNull())) - .thenThrow(new InvalidInputException("invalid input")); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - String result = sut.getClientMetadataId(context, configuration); - - assertEquals("", result); - } - - @Test - public void getClientMetadataId_whenConfigurationEnvironmentSandbox_setsMagnesEnvironmentSandbox() throws InvalidInputException { - when(configuration.getEnvironment()).thenReturn("sandbox"); - String applicationGUID = "07ea6967-1e4a-4aaa-942d-7f13db372b75"; - - HashMap additionalData = new HashMap<>(); - PayPalDataCollectorRequest payPalDataCollectorRequest = new PayPalDataCollectorRequest() - .setApplicationGuid(applicationGUID) - .setClientMetadataId("client-metadata-id") - .setAdditionalData(additionalData) - .setDisableBeacon(false); - - when(magnesSDK.collectAndSubmit(any(Context.class), eq("client-metadata-id"), same(additionalData))) - .thenReturn(mock(MagnesResult.class)); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - sut.getClientMetadataId(context, payPalDataCollectorRequest, configuration); - - ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); - verify(magnesSDK).setUp(captor.capture()); - - MagnesSettings magnesSettings = captor.getValue(); - assertEquals(Environment.SANDBOX, magnesSettings.getEnvironment()); - } - - @Test - public void getClientMetadataId_whenConfigurationEnvironmentNotSandbox_setsMagnesEnvironmentLive() throws InvalidInputException { - when(configuration.getEnvironment()).thenReturn("production"); - String applicationGUID = "07ea6967-1e4a-4aaa-942d-7f13db372b75"; - - HashMap additionalData = new HashMap<>(); - PayPalDataCollectorRequest payPalDataCollectorRequest = new PayPalDataCollectorRequest() - .setApplicationGuid(applicationGUID) - .setClientMetadataId("client-metadata-id") - .setAdditionalData(additionalData) - .setDisableBeacon(false); - - when(magnesSDK.collectAndSubmit(any(Context.class), eq("client-metadata-id"), same(additionalData))) - .thenReturn(mock(MagnesResult.class)); - - PayPalDataCollector sut = new PayPalDataCollector(magnesSDK, uuidHelper); - sut.getClientMetadataId(context, payPalDataCollectorRequest, configuration); - - ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); - verify(magnesSDK).setUp(captor.capture()); - - MagnesSettings magnesSettings = captor.getValue(); - assertEquals(Environment.LIVE, magnesSettings.getEnvironment()); - } -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c1a311a0a..ec6149b103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Braintree Android SDK Release Notes +## unreleased + +* PayPalDataCollector + * Create new module to allow for device data collection without Kount. + ## 4.13.0 * DataCollector diff --git a/Card/build.gradle b/Card/build.gradle index d46522fcdd..dd93305f35 100644 --- a/Card/build.gradle +++ b/Card/build.gradle @@ -34,7 +34,6 @@ android { dependencies { api project(':BraintreeCore') - implementation project(':BraintreeDataCollector') implementation deps.annotation diff --git a/Card/src/test/java/com/braintreepayments/api/CardClientUnitTest.java b/Card/src/test/java/com/braintreepayments/api/CardClientUnitTest.java index c2213d4565..ac609ef456 100644 --- a/Card/src/test/java/com/braintreepayments/api/CardClientUnitTest.java +++ b/Card/src/test/java/com/braintreepayments/api/CardClientUnitTest.java @@ -27,7 +27,6 @@ public class CardClientUnitTest { private Card card; private CardTokenizeCallback cardTokenizeCallback; - private DataCollector dataCollector; private ApiClient apiClient; private Configuration graphQLEnabledConfig; @@ -39,7 +38,6 @@ public void beforeEach() throws JSONException { card = new Card(); cardTokenizeCallback = mock(CardTokenizeCallback.class); - dataCollector = mock(DataCollector.class); apiClient = mock(ApiClient.class); graphQLEnabledConfig = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_GRAPHQL); diff --git a/LocalPayment/build.gradle b/LocalPayment/build.gradle index c173cf3d29..8c9a9b0f86 100644 --- a/LocalPayment/build.gradle +++ b/LocalPayment/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation deps.appCompat api project(':BraintreeCore') - implementation project(':BraintreeDataCollector') + implementation project(':PayPalDataCollector') testImplementation deps.robolectric testImplementation deps.powermockJunit diff --git a/LocalPayment/src/main/java/com/braintreepayments/api/LocalPaymentClient.java b/LocalPayment/src/main/java/com/braintreepayments/api/LocalPaymentClient.java index 712babd2f8..9d9cffb386 100644 --- a/LocalPayment/src/main/java/com/braintreepayments/api/LocalPaymentClient.java +++ b/LocalPayment/src/main/java/com/braintreepayments/api/LocalPaymentClient.java @@ -36,7 +36,7 @@ public class LocalPaymentClient { * @param braintreeClient a {@link BraintreeClient} */ public LocalPaymentClient(@NonNull FragmentActivity activity, @NonNull BraintreeClient braintreeClient) { - this(activity, activity.getLifecycle(), braintreeClient, new PayPalDataCollector(), new LocalPaymentApi(braintreeClient)); + this(activity, activity.getLifecycle(), braintreeClient, new PayPalDataCollector(braintreeClient), new LocalPaymentApi(braintreeClient)); } /** @@ -46,7 +46,7 @@ public LocalPaymentClient(@NonNull FragmentActivity activity, @NonNull Braintree * @param braintreeClient a {@link BraintreeClient} */ public LocalPaymentClient(@NonNull Fragment fragment, @NonNull BraintreeClient braintreeClient) { - this(fragment.getActivity(), fragment.getLifecycle(), braintreeClient, new PayPalDataCollector(), new LocalPaymentApi(braintreeClient)); + this(fragment.getActivity(), fragment.getLifecycle(), braintreeClient, new PayPalDataCollector(braintreeClient), new LocalPaymentApi(braintreeClient)); } /** @@ -59,7 +59,7 @@ public LocalPaymentClient(@NonNull Fragment fragment, @NonNull BraintreeClient b */ @Deprecated public LocalPaymentClient(@NonNull BraintreeClient braintreeClient) { - this(null, null, braintreeClient, new PayPalDataCollector(), new LocalPaymentApi(braintreeClient)); + this(null, null, braintreeClient, new PayPalDataCollector(braintreeClient), new LocalPaymentApi(braintreeClient)); } @VisibleForTesting diff --git a/PayPal/build.gradle b/PayPal/build.gradle index 41a4066356..84cf021fb6 100644 --- a/PayPal/build.gradle +++ b/PayPal/build.gradle @@ -35,7 +35,7 @@ dependencies { api project(':BraintreeCore') implementation deps.appCompat - implementation project(':BraintreeDataCollector') + implementation project(':PayPalDataCollector') testImplementation deps.robolectric testImplementation deps.powermockJunit diff --git a/PayPal/src/main/java/com/braintreepayments/api/PayPalInternalClient.java b/PayPal/src/main/java/com/braintreepayments/api/PayPalInternalClient.java index d659907a61..f7890fc41b 100644 --- a/PayPal/src/main/java/com/braintreepayments/api/PayPalInternalClient.java +++ b/PayPal/src/main/java/com/braintreepayments/api/PayPalInternalClient.java @@ -24,7 +24,7 @@ class PayPalInternalClient { private final ApiClient apiClient; PayPalInternalClient(BraintreeClient braintreeClient) { - this(braintreeClient, new PayPalDataCollector(), new ApiClient(braintreeClient)); + this(braintreeClient, new PayPalDataCollector(braintreeClient), new ApiClient(braintreeClient)); } @VisibleForTesting diff --git a/PayPalDataCollector/build.gradle b/PayPalDataCollector/build.gradle new file mode 100644 index 0000000000..e3c112e28c --- /dev/null +++ b/PayPalDataCollector/build.gradle @@ -0,0 +1,144 @@ +plugins { + id 'com.android.library' + id 'de.marcphilipp.nexus-publish' + id 'signing' +} + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion '30.0.3' + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + versionCode rootProject.versionCode + versionName rootProject.versionName + + consumerProguardFiles 'proguard.pro' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + + lintOptions { + textReport true + textOutput 'stdout' + } + + testOptions { + unitTests { + includeAndroidResources = true + all { + jvmArgs '-noverify' + } + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + implementation files('libs/android-magnessdk-5.3.0.jar') + + implementation deps.annotation + api project(':BraintreeCore') + + testImplementation deps.robolectric + testImplementation deps.mockitoCore + testImplementation deps.androidxTestCore + testImplementation deps.jsonAssert + testImplementation deps.powermockJunit + testImplementation deps.powermockRule + testImplementation deps.powermockMockito + testImplementation deps.powermockClassloading + + testImplementation project(':TestUtils') + + androidTestImplementation deps.androidxTestRules + androidTestImplementation deps.androidxTestRunner + androidTestImplementation deps.junitTest + androidTestImplementation deps.appCompat + androidTestImplementation project(':TestUtils') +} + +// region signing and publishing + +task javadocs(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + failOnError false +} + +task javadocsJar(type: Jar, dependsOn: javadocs) { + archiveClassifier.set('javadoc') + from javadocs.destinationDir +} + +task sourcesJar(type: Jar) { + archiveClassifier.set('sources') + from android.sourceSets.main.java.srcDirs +} + +signing { + required { + !version.endsWith("SNAPSHOT") && !version.endsWith("DEVELOPMENT") + } + sign publishing.publications +} + +nexusPublishing { + // give nexus sonatype more time to initialize the staging repository + clientTimeout = Duration.ofMinutes(3) + useStaging = !rootProject.versionName.endsWith("SNAPSHOT") + repositories { + sonatype() + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + + artifact sourcesJar + artifact javadocsJar + + groupId = 'com.braintreepayments.api' + artifactId = 'paypal-data-collector' + version = rootProject.versionName + + pom { + name = 'paypal-data-collector' + packaging = 'aar' + description = 'PayPal fraud tools for Braintree integrations on Android.' + url = 'https://github.com/braintree/braintree_android' + + scm { + url = 'scm:git@github.com:braintree/braintree_android.git' + connection = 'scm:git@github.com:braintree/braintree_android.git' + developerConnection = 'scm:git@github.com:braintree/braintree_android.git' + } + + developers { + developer { + id = 'devs' + name = 'Braintree Payments' + } + } + + licenses { + license { + name = 'MIT' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } + } + } + } + } + } +} + +// endregion diff --git a/BraintreeDataCollector/libs/android-magnessdk-5.3.0.jar b/PayPalDataCollector/libs/android-magnessdk-5.3.0.jar similarity index 100% rename from BraintreeDataCollector/libs/android-magnessdk-5.3.0.jar rename to PayPalDataCollector/libs/android-magnessdk-5.3.0.jar diff --git a/BraintreeDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java b/PayPalDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java similarity index 79% rename from BraintreeDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java rename to PayPalDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java index f999214246..8ef3449d38 100644 --- a/BraintreeDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java +++ b/PayPalDataCollector/src/androidTest/java/com/braintreepayments/api/PayPalDataCollectorTest.java @@ -17,7 +17,8 @@ public class PayPalDataCollectorTest { @Test public void getClientMetadataId_returnsClientMetadataId() throws JSONException { Configuration configuration = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_LIVE_PAYPAL); - PayPalDataCollector sut = new PayPalDataCollector(); + BraintreeClient braintreeClient = new BraintreeClient(ApplicationProvider.getApplicationContext(), Fixtures.TOKENIZATION_KEY); + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient); String clientMetadataId = sut.getClientMetadataId(ApplicationProvider.getApplicationContext(), configuration); assertFalse(TextUtils.isEmpty(clientMetadataId)); diff --git a/PayPalDataCollector/src/main/AndroidManifest.xml b/PayPalDataCollector/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..2154162398 --- /dev/null +++ b/PayPalDataCollector/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/PayPalDataCollector/src/main/java/com/braintreepayments/api/MagnesInternalClient.java b/PayPalDataCollector/src/main/java/com/braintreepayments/api/MagnesInternalClient.java new file mode 100644 index 0000000000..c60085be10 --- /dev/null +++ b/PayPalDataCollector/src/main/java/com/braintreepayments/api/MagnesInternalClient.java @@ -0,0 +1,57 @@ +package com.braintreepayments.api; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.VisibleForTesting; + +import lib.android.paypal.com.magnessdk.Environment; +import lib.android.paypal.com.magnessdk.InvalidInputException; +import lib.android.paypal.com.magnessdk.MagnesResult; +import lib.android.paypal.com.magnessdk.MagnesSDK; +import lib.android.paypal.com.magnessdk.MagnesSettings; +import lib.android.paypal.com.magnessdk.MagnesSource; + +class MagnesInternalClient { + + private final MagnesSDK magnesSDK; + + MagnesInternalClient() { + this(MagnesSDK.getInstance()); + } + + @VisibleForTesting + MagnesInternalClient(MagnesSDK magnesSDK) { + this.magnesSDK = magnesSDK; + } + + @MainThread + String getClientMetadataId(Context context, Configuration configuration, PayPalDataCollectorRequest request) { + if (context == null) { + return ""; + } + + String btEnvironment = configuration.getEnvironment(); + Environment magnesEnvironment = btEnvironment.equalsIgnoreCase("sandbox") + ? Environment.SANDBOX : Environment.LIVE; + + MagnesSettings.Builder magnesSettingsBuilder = null; + try { + magnesSettingsBuilder = new MagnesSettings.Builder(context.getApplicationContext()) + .setMagnesSource(MagnesSource.BRAINTREE) + .disableBeacon(request.isDisableBeacon()) + .setMagnesEnvironment(magnesEnvironment) + .setAppGuid(request.getApplicationGuid()); + + magnesSDK.setUp(magnesSettingsBuilder.build()); + + MagnesResult result = magnesSDK.collectAndSubmit(context.getApplicationContext(), request.getClientMetadataId(), request.getAdditionalData()); + return result.getPaypalClientMetaDataId(); + } catch (InvalidInputException e) { + // Either clientMetadataId or appGuid exceeds their character limit + Log.e("Exception", "Error fetching client metadata ID. Contact Braintree Support for assistance.", e); + return ""; + } + } +} diff --git a/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java b/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java new file mode 100644 index 0000000000..469a658b7f --- /dev/null +++ b/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollector.java @@ -0,0 +1,131 @@ +package com.braintreepayments.api; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * PayPalDataCollector is used to collect PayPal specific device information to aid in fraud detection and prevention. + */ +public class PayPalDataCollector { + + private static final String CORRELATION_ID_KEY = "correlation_id"; + + private final MagnesInternalClient magnesInternalClient; + private final UUIDHelper uuidHelper; + private final BraintreeClient braintreeClient; + + PayPalDataCollector(@NonNull BraintreeClient braintreeClient) { + this(braintreeClient, new MagnesInternalClient(), new UUIDHelper()); + } + + @VisibleForTesting + PayPalDataCollector(BraintreeClient braintreeClient, MagnesInternalClient magnesInternalClient, UUIDHelper uuidHelper) { + this.braintreeClient = braintreeClient; + this.magnesInternalClient = magnesInternalClient; + this.uuidHelper = uuidHelper; + } + + String getPayPalInstallationGUID(Context context) { + return uuidHelper.getInstallationGUID(context); + } + + /** + * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment + * from their device, PayPal uses the Client Metadata ID to verify that the payment is + * originating from a valid, user-consented device and application. This helps reduce fraud and + * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a + * "future payment") from a mobile device. Pass the result to your server, to include in the + * payment request sent to PayPal. Do not otherwise cache or store this value. + * + * @param context Android Context + * @param configuration the merchant configuration + */ + @MainThread + String getClientMetadataId(Context context, Configuration configuration) { + PayPalDataCollectorRequest request = new PayPalDataCollectorRequest() + .setApplicationGuid(getPayPalInstallationGUID(context)); + + return getClientMetadataId(context, request, configuration); + } + + /** + * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment + * from their device, PayPal uses the Client Metadata ID to verify that the payment is + * originating from a valid, user-consented device and application. This helps reduce fraud and + * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a + * "future payment") from a mobile device. Pass the result to your server, to include in the + * payment request sent to PayPal. Do not otherwise cache or store this value. + * + * @param context Android Context. + * @param request configures what data to collect. + * @param configuration the merchant configuration + */ + @MainThread + String getClientMetadataId(Context context, PayPalDataCollectorRequest request, Configuration configuration) { + return magnesInternalClient.getClientMetadataId(context, configuration, request); + } + + /** + * Collects device data based on your merchant configuration. + *

+ * We recommend that you call this method as early as possible, e.g. at app launch. If that's too early, + * call it at the beginning of customer checkout. + *

+ * Use the return value on your server, e.g. with `Transaction.sale`. + * + * @param context Android Context + * @param callback {@link PayPalDataCollectorCallback} + */ + public void collectDeviceData(@NonNull final Context context, @NonNull final PayPalDataCollectorCallback callback) { + collectDeviceData(context, null, callback); + } + + /** + * Collects device data for PayPal APIs. + *

+ * We recommend that you call this method as early as possible, e.g. at app launch. If that's too early, + * call it at the beginning of customer checkout. + *

+ * Use the return value on your server, e.g. with `Transaction.sale`. + * + * @param context Android Context + * @param clientMetadataId Optional client metadata id + * @param callback {@link PayPalDataCollectorCallback} + */ + public void collectDeviceData(@NonNull final Context context, @Nullable final String clientMetadataId, @NonNull final PayPalDataCollectorCallback callback) { + braintreeClient.getConfiguration(new ConfigurationCallback() { + @Override + public void onResult(@Nullable Configuration configuration, @Nullable Exception error) { + if (configuration != null) { + final JSONObject deviceData = new JSONObject(); + try { + PayPalDataCollectorRequest request = new PayPalDataCollectorRequest() + .setApplicationGuid(getPayPalInstallationGUID(context)); + if (clientMetadataId != null) { + request.setClientMetadataId(clientMetadataId); + } + + String correlationId = + magnesInternalClient.getClientMetadataId(context, configuration, request); + if (!TextUtils.isEmpty(correlationId)) { + deviceData.put(CORRELATION_ID_KEY, correlationId); + } + } catch (JSONException ignored) { + } + callback.onResult(deviceData.toString(), null); + + } else { + callback.onResult(null, error); + } + } + }); + } +} diff --git a/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorCallback.java b/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorCallback.java new file mode 100644 index 0000000000..8c39d9cd45 --- /dev/null +++ b/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorCallback.java @@ -0,0 +1,18 @@ +package com.braintreepayments.api; + +import android.content.Context; + +import androidx.annotation.Nullable; + +/** + * Callback for receiving result of + * {@link PayPalDataCollector#collectDeviceData(Context, PayPalDataCollectorCallback)} + */ +public interface PayPalDataCollectorCallback { + + /** + * @param deviceData the device information collected for fraud detection + * @param error an exception that occurred while fetching device data + */ + void onResult(@Nullable String deviceData, @Nullable Exception error); +} diff --git a/BraintreeDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorRequest.java b/PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorRequest.java similarity index 100% rename from BraintreeDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorRequest.java rename to PayPalDataCollector/src/main/java/com/braintreepayments/api/PayPalDataCollectorRequest.java diff --git a/PayPalDataCollector/src/test/java/com/braintreepayments/api/MagnesInternalClientUnitTest.java b/PayPalDataCollector/src/test/java/com/braintreepayments/api/MagnesInternalClientUnitTest.java new file mode 100644 index 0000000000..3f40538329 --- /dev/null +++ b/PayPalDataCollector/src/test/java/com/braintreepayments/api/MagnesInternalClientUnitTest.java @@ -0,0 +1,178 @@ +package com.braintreepayments.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; + +import java.util.HashMap; + +import lib.android.paypal.com.magnessdk.Environment; +import lib.android.paypal.com.magnessdk.InvalidInputException; +import lib.android.paypal.com.magnessdk.MagnesResult; +import lib.android.paypal.com.magnessdk.MagnesSDK; +import lib.android.paypal.com.magnessdk.MagnesSettings; +import lib.android.paypal.com.magnessdk.MagnesSource; + +@RunWith(RobolectricTestRunner.class) +public class MagnesInternalClientUnitTest { + + private Context context; + + private Configuration prodConfiguration; + private Configuration sandboxConfiguration; + + private MagnesSDK magnesSDK; + private String validApplicationGUID; + + private MagnesResult magnesResult; + + private HashMap additionalData; + private PayPalDataCollectorRequest payPalDataCollectorRequest; + + @Before + public void beforeEach() { + magnesSDK = mock(MagnesSDK.class); + context = ApplicationProvider.getApplicationContext(); + + prodConfiguration = mock(Configuration.class); + when(prodConfiguration.getEnvironment()).thenReturn("production"); + + sandboxConfiguration = mock(Configuration.class); + when(sandboxConfiguration.getEnvironment()).thenReturn("sandbox"); + + // NOTE: this uuid has no actual meaning; Magnes requires a valid guid for tests + validApplicationGUID = "0665203b-16e4-4ce2-be98-d7d73ec32e8a"; + + magnesResult = mock(MagnesResult.class); + when(magnesResult.getPaypalClientMetaDataId()).thenReturn("magnes-client-metadata-id"); + + additionalData = new HashMap<>(); + + payPalDataCollectorRequest = new PayPalDataCollectorRequest() + .setClientMetadataId("sample-client-metadata-id") + .setDisableBeacon(true) + .setAdditionalData(additionalData) + .setApplicationGuid(validApplicationGUID); + } + + @Test + public void getClientMetaDataId_returnsEmptyStringWhenContextIsNull() { + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + String result = sut.getClientMetadataId(null, sandboxConfiguration, payPalDataCollectorRequest); + assertEquals("", result); + } + + @Test + public void getClientMetaDataId_configuresMagnesSourceAsBraintree() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + sut.getClientMetadataId(context, sandboxConfiguration, payPalDataCollectorRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); + verify(magnesSDK).setUp(captor.capture()); + + MagnesSettings magnesSettings = captor.getValue(); + assertEquals(MagnesSource.BRAINTREE.getVersion(), magnesSettings.getMagnesSource()); + } + + @Test + public void getClientMetaDataId_whenBraintreeEnvironmentIsSandbox_configuresMagnesEnvironmentToSandbox() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + sut.getClientMetadataId(context, sandboxConfiguration, payPalDataCollectorRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); + verify(magnesSDK).setUp(captor.capture()); + + MagnesSettings magnesSettings = captor.getValue(); + assertEquals(Environment.SANDBOX, magnesSettings.getEnvironment()); + } + + @Test + public void getClientMetaDataId_whenBraintreeEnvironmentIsProd_configuresMagnesEnvironmentToLive() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + sut.getClientMetadataId(context, prodConfiguration, payPalDataCollectorRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); + verify(magnesSDK).setUp(captor.capture()); + + MagnesSettings magnesSettings = captor.getValue(); + assertEquals(Environment.LIVE, magnesSettings.getEnvironment()); + } + + @Test + public void getClientMetaDataId_forwardsDisableBeaconOptionToMagnes() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + sut.getClientMetadataId(context, prodConfiguration, payPalDataCollectorRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); + verify(magnesSDK).setUp(captor.capture()); + + MagnesSettings magnesSettings = captor.getValue(); + assertTrue(magnesSettings.isDisableBeacon()); + } + + @Test + public void getClientMetaDataId_forwardsApplicationGUIDOptionToMagnes() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + sut.getClientMetadataId(context, prodConfiguration, payPalDataCollectorRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MagnesSettings.class); + verify(magnesSDK).setUp(captor.capture()); + + MagnesSettings magnesSettings = captor.getValue(); + assertEquals(validApplicationGUID, magnesSettings.getAppGuid()); + } + + @Test + public void getClientMetaDataId_returnsAnEmptyStringWhenApplicationGUIDIsInvalid() { + PayPalDataCollectorRequest requestWithInvalidGUID = new PayPalDataCollectorRequest() + .setApplicationGuid("invalid guid"); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + String result = sut.getClientMetadataId(context, prodConfiguration, requestWithInvalidGUID); + + assertEquals("", result); + } + + @Test + public void getClientMetaDataId_forwardsClientMetadataIdFromMagnesStart() throws InvalidInputException { + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenReturn(magnesResult); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + String result = sut.getClientMetadataId(context, prodConfiguration, payPalDataCollectorRequest); + + assertEquals("magnes-client-metadata-id", result); + } + + @Test + public void getClientMetaDataId_returnsAnEmptyStringWhenCollectAndSubmitThrows() throws InvalidInputException { + + when(magnesSDK.collectAndSubmit(context, "sample-client-metadata-id", additionalData)).thenThrow(new InvalidInputException("invalid input")); + + MagnesInternalClient sut = new MagnesInternalClient(magnesSDK); + String result = sut.getClientMetadataId(context, prodConfiguration, payPalDataCollectorRequest); + + assertEquals("", result); + } +} \ No newline at end of file diff --git a/BraintreeDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorRequestUnitTest.java b/PayPalDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorRequestUnitTest.java similarity index 100% rename from BraintreeDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorRequestUnitTest.java rename to PayPalDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorRequestUnitTest.java diff --git a/PayPalDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java b/PayPalDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java new file mode 100644 index 0000000000..8ecdc745a9 --- /dev/null +++ b/PayPalDataCollector/src/test/java/com/braintreepayments/api/PayPalDataCollectorUnitTest.java @@ -0,0 +1,180 @@ +package com.braintreepayments.api; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class PayPalDataCollectorUnitTest { + + private Context context; + private String sampleInstallationGUID; + private Configuration configuration; + + private UUIDHelper uuidHelper; + + private BraintreeClient braintreeClient; + private MagnesInternalClient magnesInternalClient; + + @Before + public void beforeEach() throws JSONException { + uuidHelper = mock(UUIDHelper.class); + magnesInternalClient = mock(MagnesInternalClient.class); + + context = ApplicationProvider.getApplicationContext(); + + configuration = mock(Configuration.class); + + // this uuid has no actual meaning; magnes requires a valid guid for tests + sampleInstallationGUID = "0665203b-16e4-4ce2-be98-d7d73ec32e8a"; + + braintreeClient = new MockBraintreeClientBuilder() + .configuration(Configuration.fromJson(Fixtures.CONFIGURATION_WITHOUT_ACCESS_TOKEN)) + .build(); + } + + @Test + public void getPayPalInstallationGUID_returnsInstallationIdentifier() { + + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + + assertEquals(sampleInstallationGUID, sut.getPayPalInstallationGUID(context)); + } + + @Test + public void getClientMetadataId_configuresMagnesWithDefaultRequest() { + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + + when(magnesInternalClient.getClientMetadataId(same(context), same(configuration), any(PayPalDataCollectorRequest.class))).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + sut.getClientMetadataId(context, configuration); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PayPalDataCollectorRequest.class); + verify(magnesInternalClient).getClientMetadataId(same(context), same(configuration), captor.capture()); + + PayPalDataCollectorRequest request = captor.getValue(); + assertEquals(sampleInstallationGUID, request.getApplicationGuid()); + } + + @Test + public void getClientMetadataId_configuresMagnesWithCustomRequestAndForwardsClientMetadataIdFromMagnesResult() { + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + + PayPalDataCollectorRequest customRequest = new PayPalDataCollectorRequest(); + when(magnesInternalClient.getClientMetadataId(context, configuration, customRequest)).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + sut.getClientMetadataId(context, customRequest, configuration); + + verify(magnesInternalClient).getClientMetadataId(same(context), same(configuration), same(customRequest)); + } + + @Test + public void getClientMetadataId_forwardsClientMetadataIdFromMagnesResult() { + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + + when(magnesInternalClient.getClientMetadataId(same(context), same(configuration), any(PayPalDataCollectorRequest.class))).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + String result = sut.getClientMetadataId(context, configuration); + + assertEquals("paypal-clientmetadata-id", result); + } + + @Test + public void collectDeviceData_forwardsConfigurationFetchErrors() { + Exception configError = new Exception("configuration error"); + BraintreeClient braintreeClient = new MockBraintreeClientBuilder() + .configurationError(configError) + .build(); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + + PayPalDataCollectorCallback callback = mock(PayPalDataCollectorCallback.class); + sut.collectDeviceData(context, callback); + + verify(callback).onResult(null, configError); + } + + @Test + public void collectDeviceData_configuresMagnesWithDefaultRequest() { + BraintreeClient braintreeClient = new MockBraintreeClientBuilder() + .configuration(configuration) + .build(); + + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + when(magnesInternalClient.getClientMetadataId(same(context), same(configuration), any(PayPalDataCollectorRequest.class))).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + + PayPalDataCollectorCallback callback = mock(PayPalDataCollectorCallback.class); + sut.collectDeviceData(context, callback); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PayPalDataCollectorRequest.class); + verify(magnesInternalClient).getClientMetadataId(same(context), same(configuration), captor.capture()); + + PayPalDataCollectorRequest request = captor.getValue(); + assertEquals(sampleInstallationGUID, request.getApplicationGuid()); + } + + @Test + public void collectDeviceData_configuresMagnesWithClientId() { + BraintreeClient braintreeClient = new MockBraintreeClientBuilder() + .configuration(configuration) + .build(); + + when(uuidHelper.getInstallationGUID(context)).thenReturn(sampleInstallationGUID); + when(magnesInternalClient.getClientMetadataId(same(context), same(configuration), any(PayPalDataCollectorRequest.class))).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + + PayPalDataCollectorCallback callback = mock(PayPalDataCollectorCallback.class); + sut.collectDeviceData(context, "custom-client-metadata-id", callback); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PayPalDataCollectorRequest.class); + verify(magnesInternalClient).getClientMetadataId(same(context), same(configuration), captor.capture()); + + PayPalDataCollectorRequest request = captor.getValue(); + assertEquals(sampleInstallationGUID, request.getApplicationGuid()); + assertEquals("custom-client-metadata-id", request.getClientMetadataId()); + } + + @Test + public void collectDeviceData_getsDeviceDataJSONWithCorrelationIdFromPayPal() throws Exception { + BraintreeClient braintreeClient = new MockBraintreeClientBuilder() + .configuration(configuration) + .build(); + + when(magnesInternalClient.getClientMetadataId(same(context), same(configuration), any(PayPalDataCollectorRequest.class))).thenReturn("paypal-clientmetadata-id"); + + PayPalDataCollector sut = new PayPalDataCollector(braintreeClient, magnesInternalClient, uuidHelper); + + PayPalDataCollectorCallback callback = mock(PayPalDataCollectorCallback.class); + sut.collectDeviceData(context, callback); + + ArgumentCaptor deviceDataCaptor = ArgumentCaptor.forClass(String.class); + verify(callback).onResult(deviceDataCaptor.capture(), (Exception) isNull()); + + String deviceData = deviceDataCaptor.getValue(); + JSONObject json = new JSONObject(deviceData); + assertEquals("paypal-clientmetadata-id", json.getString("correlation_id")); + } +} diff --git a/PayPalDataCollector/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/PayPalDataCollector/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/PayPalDataCollector/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/PayPalDataCollector/src/test/resources/robolectric.properties b/PayPalDataCollector/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..932b01b9eb --- /dev/null +++ b/PayPalDataCollector/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=28 diff --git a/PayPalNativeCheckout/build.gradle b/PayPalNativeCheckout/build.gradle index 5cb3428b95..3025ac9e21 100644 --- a/PayPalNativeCheckout/build.gradle +++ b/PayPalNativeCheckout/build.gradle @@ -35,7 +35,7 @@ dependencies { api project(':BraintreeCore') implementation deps.appCompat - implementation project(':BraintreeDataCollector') + implementation project(':PayPalDataCollector') implementation('com.paypal.checkout:android-sdk:0.7.2') { exclude group: 'com.paypal.android.sdk', module: 'data-collector' diff --git a/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutAccount.java b/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutAccount.java index f716143f8a..5c7e0e6786 100644 --- a/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutAccount.java +++ b/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutAccount.java @@ -64,7 +64,7 @@ JSONObject buildJSON() throws JSONException { * Used by PayPal wrappers to construct a request to create a PayPal account. * * @param clientMetadataId Application clientMetadataId created by - * {@link com.braintreepayments.api.PayPalDataCollector#getClientMetadataId(Context, Configuration)}. + * {@link PayPalDataCollector#getClientMetadataId(Context, Configuration)}. */ void setClientMetadataId(String clientMetadataId) { this.clientMetadataId = clientMetadataId; diff --git a/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutInternalClient.java b/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutInternalClient.java index 9b90394478..a9e855c14f 100644 --- a/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutInternalClient.java +++ b/PayPalNativeCheckout/src/main/java/com/braintreepayments/api/PayPalNativeCheckoutInternalClient.java @@ -25,7 +25,7 @@ interface PayPalNativeCheckoutInternalClientCallback { } PayPalNativeCheckoutInternalClient(BraintreeClient braintreeClient) { - this(braintreeClient, new PayPalDataCollector(), new ApiClient(braintreeClient)); + this(braintreeClient, new PayPalDataCollector(braintreeClient), new ApiClient(braintreeClient)); } @VisibleForTesting diff --git a/settings.gradle b/settings.gradle index e9566f0f58..69ddff8106 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,4 @@ include ':ThreeDSecure' include ':TestUtils' include ':Demo' include ':SEPADirectDebit' +include ':PayPalDataCollector'