diff --git a/CHANGELOG.md b/CHANGELOG.md
index a369268f..79c1bd27 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Braintree Android Drop-In Release Notes
+## unreleased
+
+* Bump braintree_android module dependency versions to `4.45.0`
+* Fixes Google Play Store Rejection
+ * Add `hasUserLocationConsent` property to `DropInRequest`
+ * Deprecate existing constructor that does not pass in `hasUserLocationConsent`
+
## 6.15.0
* Refresh vaulted payment methods list after 3DS is canceled (fixes #455)
diff --git a/Drop-In/src/main/java/com/braintreepayments/api/DropInActivity.java b/Drop-In/src/main/java/com/braintreepayments/api/DropInActivity.java
index c42e7115..ff534610 100644
--- a/Drop-In/src/main/java/com/braintreepayments/api/DropInActivity.java
+++ b/Drop-In/src/main/java/com/braintreepayments/api/DropInActivity.java
@@ -487,7 +487,7 @@ private void onVaultedPaymentMethodSelected(DropInEvent event) {
} else {
final DropInResult dropInResult = new DropInResult();
dropInResult.setPaymentMethodNonce(paymentMethodNonce);
- dropInInternalClient.collectDeviceData(DropInActivity.this, (deviceData, error) -> {
+ dropInInternalClient.collectDeviceData(DropInActivity.this, dropInRequest.hasUserLocationConsent(), (deviceData, error) -> {
if (deviceData != null) {
dropInResult.setDeviceData(deviceData);
animateBottomSheetClosedAndFinishDropInWithResult(dropInResult);
@@ -531,7 +531,7 @@ private void onPaymentMethodNonceCreated(final PaymentMethodNonce paymentMethod)
} else {
DropInResult dropInResult = new DropInResult();
dropInResult.setPaymentMethodNonce(paymentMethod);
- dropInInternalClient.collectDeviceData(this, (deviceData, deviceDataError) -> {
+ dropInInternalClient.collectDeviceData(this, dropInRequest.hasUserLocationConsent(), (deviceData, deviceDataError) -> {
if (deviceData != null) {
dropInResult.setDeviceData(deviceData);
animateBottomSheetClosedAndFinishDropInWithResult(dropInResult);
diff --git a/Drop-In/src/main/java/com/braintreepayments/api/DropInInternalClient.java b/Drop-In/src/main/java/com/braintreepayments/api/DropInInternalClient.java
index 0560ea8c..e6a79e83 100644
--- a/Drop-In/src/main/java/com/braintreepayments/api/DropInInternalClient.java
+++ b/Drop-In/src/main/java/com/braintreepayments/api/DropInInternalClient.java
@@ -90,8 +90,13 @@ void sendAnalyticsEvent(String eventName) {
braintreeClient.sendAnalyticsEvent(eventName);
}
- void collectDeviceData(FragmentActivity activity, DataCollectorCallback callback) {
- dataCollector.collectDeviceData(activity, callback);
+ void collectDeviceData(
+ FragmentActivity activity,
+ boolean hasUserLocationConsent,
+ DataCollectorCallback callback
+ ) {
+ DataCollectorRequest request = new DataCollectorRequest(hasUserLocationConsent);
+ dataCollector.collectDeviceData(activity, request, callback);
}
void performThreeDSecureVerification(final FragmentActivity activity, PaymentMethodNonce paymentMethodNonce, final DropInResultCallback callback) {
@@ -106,7 +111,7 @@ void performThreeDSecureVerification(final FragmentActivity activity, PaymentMet
} else if (threeDSecureResult != null) {
final DropInResult dropInResult = new DropInResult();
dropInResult.setPaymentMethodNonce(threeDSecureResult.getTokenizedCard());
- dataCollector.collectDeviceData(activity, (deviceData, dataCollectionError) -> {
+ collectDeviceData(activity, dropInRequest.hasUserLocationConsent(), (deviceData, dataCollectionError) -> {
if (deviceData != null) {
dropInResult.setDeviceData(deviceData);
callback.onResult(dropInResult, null);
@@ -143,7 +148,7 @@ void shouldRequestThreeDSecureVerification(PaymentMethodNonce paymentMethodNonce
void tokenizePayPalRequest(FragmentActivity activity, PayPalFlowStartedCallback callback) {
PayPalRequest paypalRequest = dropInRequest.getPayPalRequest();
if (paypalRequest == null) {
- paypalRequest = new PayPalVaultRequest();
+ paypalRequest = new PayPalVaultRequest(dropInRequest.hasUserLocationConsent());
}
payPalClient.tokenizePayPalAccount(activity, paypalRequest, callback);
}
@@ -248,7 +253,7 @@ private void notifyDropInResult(FragmentActivity activity, PaymentMethodNonce pa
final DropInResult dropInResult = new DropInResult();
dropInResult.setPaymentMethodNonce(paymentMethodNonce);
- dataCollector.collectDeviceData(activity, (deviceData, dataCollectionError) -> {
+ collectDeviceData(activity, dropInRequest.hasUserLocationConsent(), (deviceData, dataCollectionError) -> {
if (dataCollectionError != null) {
callback.onResult(null, dataCollectionError);
return;
diff --git a/Drop-In/src/main/java/com/braintreepayments/api/DropInRequest.java b/Drop-In/src/main/java/com/braintreepayments/api/DropInRequest.java
index 93f7e0a6..69bd6467 100644
--- a/Drop-In/src/main/java/com/braintreepayments/api/DropInRequest.java
+++ b/Drop-In/src/main/java/com/braintreepayments/api/DropInRequest.java
@@ -29,10 +29,30 @@ public class DropInRequest implements Parcelable {
private boolean allowVaultCardOverride = false;
private String customUrlScheme = null;
+ private final boolean hasUserLocationConsent;
private int cardholderNameStatus = CardForm.FIELD_DISABLED;
- public DropInRequest() {}
+ /**
+ * Deprecated. Use {@link DropInRequest#DropInRequest(boolean)} instead.
+ **/
+ @Deprecated
+ public DropInRequest() {
+ hasUserLocationConsent = false;
+ }
+
+ /**
+ * @param hasUserLocationConsent informs the SDK if your application has obtained consent from
+ * the user to collect location data in compliance with
+ * Google Play Developer Program policies
+ * This flag enables PayPal to collect necessary information required for Fraud Detection and Risk Management.
+ *
+ * @see User Data policies for the Google Play Developer Program
+ * @see Examples of prominent in-app disclosures
+ */
+ public DropInRequest(boolean hasUserLocationConsent) {
+ this.hasUserLocationConsent = hasUserLocationConsent;
+ }
/**
* This method is optional.
@@ -311,6 +331,13 @@ public String getCustomUrlScheme() {
return customUrlScheme;
}
+ /**
+ * @return If the user has consented to sharing location data.
+ */
+ boolean hasUserLocationConsent() {
+ return hasUserLocationConsent;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -334,6 +361,7 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeByte(vaultCardDefaultValue ? (byte) 1 : (byte) 0);
dest.writeByte(allowVaultCardOverride ? (byte) 1 : (byte) 0);
dest.writeString(customUrlScheme);
+ dest.writeByte(hasUserLocationConsent ? (byte) 1 : (byte) 0);
}
protected DropInRequest(Parcel in) {
@@ -353,6 +381,7 @@ protected DropInRequest(Parcel in) {
vaultCardDefaultValue = in.readByte() != 0;
allowVaultCardOverride = in.readByte() != 0;
customUrlScheme = in.readString();
+ hasUserLocationConsent = in.readByte() != 0;
}
public static final Creator CREATOR = new Creator() {
diff --git a/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDataCollectorBuilder.java b/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDataCollectorBuilder.java
index b18fb89f..88eef077 100644
--- a/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDataCollectorBuilder.java
+++ b/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDataCollectorBuilder.java
@@ -27,14 +27,18 @@ public DataCollector build() {
DataCollector dataCollector = mock(DataCollector.class);
doAnswer((Answer) invocation -> {
- DataCollectorCallback callback = (DataCollectorCallback) invocation.getArguments()[1];
+ DataCollectorCallback callback = (DataCollectorCallback) invocation.getArguments()[2];
if (collectDeviceDataSuccess != null) {
callback.onResult(collectDeviceDataSuccess, null);
} else if (collectDeviceDataError != null) {
callback.onResult(null, collectDeviceDataError);
}
return null;
- }).when(dataCollector).collectDeviceData(any(Context.class), any(DataCollectorCallback.class));
+ }).when(dataCollector).collectDeviceData(
+ any(Context.class),
+ any(DataCollectorRequest.class),
+ any(DataCollectorCallback.class)
+ );
return dataCollector;
}
diff --git a/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDropInInternalClientBuilder.java b/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDropInInternalClientBuilder.java
index 3042faf6..e9d02f7a 100644
--- a/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDropInInternalClientBuilder.java
+++ b/Drop-In/src/sharedTest/java/com/braintreepayments/api/MockDropInInternalClientBuilder.java
@@ -1,6 +1,7 @@
package com.braintreepayments.api;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -253,14 +254,14 @@ DropInInternalClient build() {
}).when(dropInClient).getSupportedCardTypes(any(GetSupportedCardTypesCallback.class));
doAnswer((Answer) invocation -> {
- DataCollectorCallback callback = (DataCollectorCallback) invocation.getArguments()[1];
+ DataCollectorCallback callback = (DataCollectorCallback) invocation.getArguments()[2];
if (deviceDataSuccess != null) {
callback.onResult(deviceDataSuccess, null);
} else if (deviceDataError != null) {
callback.onResult(null, deviceDataError);
}
return null;
- }).when(dropInClient).collectDeviceData(any(FragmentActivity.class), any(DataCollectorCallback.class));
+ }).when(dropInClient).collectDeviceData(any(FragmentActivity.class), anyBoolean(), any(DataCollectorCallback.class));
doAnswer((Answer) invocation -> {
DeletePaymentMethodNonceCallback callback = (DeletePaymentMethodNonceCallback) invocation.getArguments()[2];
diff --git a/Drop-In/src/test/java/com/braintreepayments/api/DropInActivityUnitTest.kt b/Drop-In/src/test/java/com/braintreepayments/api/DropInActivityUnitTest.kt
index 9d22afb1..f5ac588a 100644
--- a/Drop-In/src/test/java/com/braintreepayments/api/DropInActivityUnitTest.kt
+++ b/Drop-In/src/test/java/com/braintreepayments/api/DropInActivityUnitTest.kt
@@ -35,7 +35,7 @@ class DropInActivityUnitTest {
@Before
fun beforeEach() {
authorization = Authorization.fromString(Fixtures.TOKENIZATION_KEY)
- dropInRequest = DropInRequest()
+ dropInRequest = DropInRequest(true)
}
@After
@@ -425,6 +425,39 @@ class DropInActivityUnitTest {
)
}
+ @Test
+ fun threeDS_collectDeviceData_passes_in_hasUserLocationConsent() {
+ val dropInClient = MockDropInInternalClientBuilder()
+ .authorizationSuccess(authorization)
+ .shouldPerformThreeDSecureVerification(false)
+ .build()
+ setupDropInActivity(dropInClient, dropInRequest)
+
+ val cardNonce = CardNonce.fromJSON(JSONObject(Fixtures.VISA_CREDIT_CARD_RESPONSE))
+ activity.supportFragmentManager.setFragmentResult(
+ DropInEvent.REQUEST_KEY,
+ DropInEvent.createVaultedPaymentMethodSelectedEvent(cardNonce).toBundle()
+ )
+
+ verify(dropInClient).collectDeviceData(same(activity), eq(true), any())
+ }
+
+ @Test
+ fun card_collectDeviceData_passes_in_hasUserLocationConsent() {
+ val cardNonce = CardNonce.fromJSON(JSONObject(Fixtures.VISA_CREDIT_CARD_RESPONSE))
+ val dropInClient = MockDropInInternalClientBuilder()
+ .authorizationSuccess(authorization)
+ .shouldPerformThreeDSecureVerification(false)
+ .cardTokenizeSuccess(cardNonce)
+ .build()
+ setupDropInActivity(dropInClient, dropInRequest)
+
+ val event = DropInEvent.createCardDetailsSubmitEvent(Card())
+ activity.supportFragmentManager.setFragmentResult(DropInEvent.REQUEST_KEY, event.toBundle())
+
+ verify(dropInClient).collectDeviceData(same(activity), eq(true), any())
+ }
+
@Test
fun onVaultedPaymentMethodSelectedEvent_returnsDeviceData() {
val dropInClient = MockDropInInternalClientBuilder()
diff --git a/Drop-In/src/test/java/com/braintreepayments/api/DropInInternalClientUnitTest.java b/Drop-In/src/test/java/com/braintreepayments/api/DropInInternalClientUnitTest.java
index c9d6233e..7cec8598 100644
--- a/Drop-In/src/test/java/com/braintreepayments/api/DropInInternalClientUnitTest.java
+++ b/Drop-In/src/test/java/com/braintreepayments/api/DropInInternalClientUnitTest.java
@@ -127,9 +127,10 @@ public void collectDeviceData_forwardsInvocationToDataCollector() {
DataCollectorCallback callback = mock(DataCollectorCallback.class);
DropInInternalClient sut = new DropInInternalClient(params);
- sut.collectDeviceData(activity, callback);
+ sut.collectDeviceData(activity, true, callback);
- verify(dataCollector).collectDeviceData(activity, callback);
+ DataCollectorRequest request = new DataCollectorRequest(true);
+ verify(dataCollector).collectDeviceData(activity, request, callback);
}
@Test
@@ -858,6 +859,26 @@ public void tokenizePayPalAccount_withPayPalVaultRequest_tokenizesPayPalWithVaul
verify(payPalClient).tokenizePayPalAccount(same(activity), same(payPalRequest), same(callback));
}
+ @Test
+ public void tokenizePayPalRequest_when_dropInRequest_is_null_hasUserLocationConsent_is_set_from_dropInRequest() {
+ BraintreeClient braintreeClient = new MockBraintreeClientBuilder().build();
+ DropInRequest dropInRequest = new DropInRequest(true);
+ PayPalClient payPalClient = mock(PayPalClient.class);
+ DropInInternalClientParams params = new DropInInternalClientParams()
+ .dropInRequest(dropInRequest)
+ .payPalClient(payPalClient)
+ .braintreeClient(braintreeClient);
+
+ PayPalFlowStartedCallback callback = mock(PayPalFlowStartedCallback.class);
+ DropInInternalClient sut = new DropInInternalClient(params);
+
+ sut.tokenizePayPalRequest(activity, callback);
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(PayPalRequest.class);
+ verify(payPalClient).tokenizePayPalAccount(same(activity), captor.capture(), same(callback));
+ assertTrue(captor.getValue().hasUserLocationConsent());
+ }
+
@Test
public void tokenizeVenmoAccount_tokenizesVenmo() {
Configuration configuration = mockConfiguration(false, true, false, false, false);
diff --git a/Drop-In/src/test/java/com/braintreepayments/api/DropInRequestUnitTest.java b/Drop-In/src/test/java/com/braintreepayments/api/DropInRequestUnitTest.java
index 3d31baf5..56ae3869 100644
--- a/Drop-In/src/test/java/com/braintreepayments/api/DropInRequestUnitTest.java
+++ b/Drop-In/src/test/java/com/braintreepayments/api/DropInRequestUnitTest.java
@@ -12,6 +12,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
@RunWith(RobolectricTestRunner.class)
@@ -58,7 +59,7 @@ public void includesAllOptions() {
additionalInformation.setShippingMethodIndicator("GEN");
threeDSecureRequest.setAdditionalInformation(additionalInformation);
- DropInRequest dropInRequest = new DropInRequest();
+ DropInRequest dropInRequest = new DropInRequest(true);
dropInRequest.setGooglePayRequest(googlePayRequest);
dropInRequest.setGooglePayDisabled(true);
dropInRequest.setPayPalRequest(paypalRequest);
@@ -120,6 +121,7 @@ public void includesAllOptions() {
assertTrue(dropInRequest.getVaultCardDefaultValue());
assertTrue(dropInRequest.getAllowVaultCardOverride());
assertEquals(CardForm.FIELD_OPTIONAL, dropInRequest.getCardholderNameStatus());
+ assertTrue(dropInRequest.hasUserLocationConsent());
}
@Test
@@ -164,7 +166,7 @@ public void isParcelable() {
additionalInformation.setShippingMethodIndicator("GEN");
threeDSecureRequest.setAdditionalInformation(additionalInformation);
- DropInRequest dropInRequest = new DropInRequest();
+ DropInRequest dropInRequest = new DropInRequest(true);
dropInRequest.setGooglePayRequest(googlePayRequest);
dropInRequest.setGooglePayDisabled(true);
dropInRequest.setPayPalRequest(paypalRequest);
@@ -229,13 +231,20 @@ public void isParcelable() {
assertTrue(parceledDropInRequest.getVaultCardDefaultValue());
assertTrue(parceledDropInRequest.getAllowVaultCardOverride());
assertEquals(CardForm.FIELD_OPTIONAL, parceledDropInRequest.getCardholderNameStatus());
+ assertTrue(parceledDropInRequest.hasUserLocationConsent());
}
@Test
public void getCardholderNameStatus_includesCardHolderNameStatus() {
- DropInRequest dropInRequest = new DropInRequest();
+ DropInRequest dropInRequest = new DropInRequest(true);
dropInRequest.setCardholderNameStatus(CardForm.FIELD_REQUIRED);
assertEquals(CardForm.FIELD_REQUIRED, dropInRequest.getCardholderNameStatus());
}
+
+ @Test
+ public void no_argument_constructor_defaults_hasUserLocationConsent_to_false() {
+ DropInRequest request = new DropInRequest();
+ assertFalse(request.hasUserLocationConsent());
+ }
}
diff --git a/build.gradle b/build.gradle
index c06dab0d..5c3897df 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
}
- ext.brainTreeVersion = "4.41.0"
+ ext.brainTreeVersion = "4.45.0"
ext.deps = [
"braintreeCore" : "com.braintreepayments.api:braintree-core:$brainTreeVersion",