diff --git a/AmericanExpress/build.gradle b/AmericanExpress/build.gradle
index 8cd2a9fdb9..903e0dba70 100644
--- a/AmericanExpress/build.gradle
+++ b/AmericanExpress/build.gradle
@@ -39,6 +39,7 @@ dependencies {
testImplementation libs.robolectric
testImplementation libs.mockito.core
testImplementation libs.json.assert
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
}
diff --git a/BraintreeCore/build.gradle b/BraintreeCore/build.gradle
index 180dedf6c8..cc5debecff 100644
--- a/BraintreeCore/build.gradle
+++ b/BraintreeCore/build.gradle
@@ -87,6 +87,7 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.json.assert
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation libs.kotlin.test
testImplementation project(':PayPal')
testImplementation project(':TestUtils')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53d94d6c93..2cf11a56a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Braintree Android SDK Release Notes
+## unreleased
+
+* PayPal
+ * Add PayPal App Switch for one time checkout flow (BETA)
+ * Add `enablePayPalAppSwitch` property to `PayPalCheckoutRequest` for App Switch support
+ * Require `PayPalCheckoutRequest.userAuthenticationEmail` for App Switch support
+ * Require `PayPalClient.appLinkReturnUrl` for App Switch support
+ * **Note:** This feature is currently in beta and may change or be removed in future releases.
+
## 5.2.0 (2024-10-30)
* GooglePay
diff --git a/Card/build.gradle b/Card/build.gradle
index 337fe77e33..7ff1504048 100644
--- a/Card/build.gradle
+++ b/Card/build.gradle
@@ -44,6 +44,7 @@ dependencies {
testImplementation libs.androidx.core.ktx
testImplementation libs.kotlin.stdlib
testImplementation libs.kotlin.test
+ testImplementation libs.test.parameter.injector
androidTestImplementation project(':TestUtils')
androidTestImplementation libs.androidx.test.rules
diff --git a/DataCollector/build.gradle b/DataCollector/build.gradle
index f5ac385074..d7d55bcd1b 100644
--- a/DataCollector/build.gradle
+++ b/DataCollector/build.gradle
@@ -48,6 +48,7 @@ dependencies {
testImplementation libs.json.assert
testImplementation libs.mockito.core
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
diff --git a/Demo/build.gradle b/Demo/build.gradle
index 27a7e69a61..b1c0b6d8ce 100644
--- a/Demo/build.gradle
+++ b/Demo/build.gradle
@@ -106,6 +106,7 @@ dependencies {
androidTestImplementation project(':TestUtils')
testImplementation libs.junit
+ testImplementation libs.test.parameter.injector
}
task demoApkSize {
diff --git a/Demo/src/main/java/com/braintreepayments/demo/PayPalRequestFactory.java b/Demo/src/main/java/com/braintreepayments/demo/PayPalRequestFactory.java
index efdc0d8bd5..2318c09390 100644
--- a/Demo/src/main/java/com/braintreepayments/demo/PayPalRequestFactory.java
+++ b/Demo/src/main/java/com/braintreepayments/demo/PayPalRequestFactory.java
@@ -123,6 +123,10 @@ public static PayPalCheckoutRequest createPayPalCheckoutRequest(
request.setUserPhoneNumber(new PayPalPhoneNumber(buyerPhoneCountryCode, buyerPhoneNationalNumber));
}
+ if (Settings.isPayPalAppSwithEnabled(context)) {
+ request.setEnablePayPalAppSwitch(true);
+ }
+
request.setDisplayName(Settings.getPayPalDisplayName(context));
String landingPageType = Settings.getPayPalLandingPageType(context);
diff --git a/Demo/src/main/res/values/strings.xml b/Demo/src/main/res/values/strings.xml
index d3272218a6..9815484262 100644
--- a/Demo/src/main/res/values/strings.xml
+++ b/Demo/src/main/res/values/strings.xml
@@ -8,7 +8,7 @@
Nonce: %s
Device Data: %s
Google Pay
- PayPal Web
+ PayPal
Visa Checkout
Pay with iDEAL
Local Payment
diff --git a/GooglePay/build.gradle b/GooglePay/build.gradle
index dc95ea28b4..918d79d222 100644
--- a/GooglePay/build.gradle
+++ b/GooglePay/build.gradle
@@ -55,6 +55,7 @@ dependencies {
testImplementation libs.kotlin.stdlib
testImplementation libs.coroutines.core
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation libs.androidx.test.core
}
diff --git a/LocalPayment/build.gradle b/LocalPayment/build.gradle
index 36ffebe470..7871cb7635 100644
--- a/LocalPayment/build.gradle
+++ b/LocalPayment/build.gradle
@@ -41,6 +41,7 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.json.assert
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
androidTestImplementation project(':TestUtils')
diff --git a/PayPal/build.gradle b/PayPal/build.gradle
index 850bcfc88d..7065231883 100644
--- a/PayPal/build.gradle
+++ b/PayPal/build.gradle
@@ -42,6 +42,7 @@ dependencies {
testImplementation libs.json.assert
testImplementation libs.mockito.core
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
}
diff --git a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalCheckoutRequest.kt b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalCheckoutRequest.kt
index e95d708813..1ab0bec7cb 100644
--- a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalCheckoutRequest.kt
+++ b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalCheckoutRequest.kt
@@ -1,5 +1,6 @@
package com.braintreepayments.api.paypal
+import android.os.Build
import android.text.TextUtils
import com.braintreepayments.api.core.Authorization
import com.braintreepayments.api.core.ClientToken
@@ -64,6 +65,7 @@ class PayPalCheckoutRequest @JvmOverloads constructor(
var currencyCode: String? = null,
var shouldRequestBillingAgreement: Boolean = false,
var shouldOfferPayLater: Boolean = false,
+ override var enablePayPalAppSwitch: Boolean = false,
override var localeCode: String? = null,
override var billingAgreementDescription: String? = null,
override var isShippingAddressRequired: Boolean = false,
@@ -87,6 +89,7 @@ class PayPalCheckoutRequest @JvmOverloads constructor(
displayName = displayName,
merchantAccountId = merchantAccountId,
riskCorrelationId = riskCorrelationId,
+ enablePayPalAppSwitch = enablePayPalAppSwitch,
userAuthenticationEmail = userAuthenticationEmail,
lineItems = lineItems
) {
@@ -126,6 +129,13 @@ class PayPalCheckoutRequest @JvmOverloads constructor(
userPhoneNumber?.let { parameters.put(PHONE_NUMBER_KEY, it.toJson()) }
+ if (enablePayPalAppSwitch && !appLink.isNullOrEmpty() && !userAuthenticationEmail.isNullOrEmpty()) {
+ parameters.put(ENABLE_APP_SWITCH_KEY, enablePayPalAppSwitch)
+ parameters.put(OS_VERSION_KEY, Build.VERSION.SDK_INT.toString())
+ parameters.put(OS_TYPE_KEY, "Android")
+ parameters.put(MERCHANT_APP_RETURN_URL_KEY, appLink)
+ }
+
if (currencyCode == null) {
currencyCode = configuration?.payPalCurrencyIsoCode
}
diff --git a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalInternalClient.kt b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalInternalClient.kt
index cfb9427d40..afd4c99704 100644
--- a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalInternalClient.kt
+++ b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalInternalClient.kt
@@ -44,9 +44,8 @@ internal class PayPalInternalClient(
CREATE_SINGLE_PAYMENT_ENDPOINT
}
val url = "/v1/$endpoint"
- val appLinkReturn = if (isBillingAgreement) appLink else null
- if (isBillingAgreement && (payPalRequest as PayPalVaultRequest).enablePayPalAppSwitch) {
+ if (payPalRequest.enablePayPalAppSwitch) {
payPalRequest.enablePayPalAppSwitch = isPayPalInstalled(context)
}
@@ -55,7 +54,7 @@ internal class PayPalInternalClient(
authorization = merchantRepository.authorization,
successUrl = successUrl,
cancelUrl = cancelUrl,
- appLink = appLinkReturn
+ appLink = appLink
) ?: throw JSONException("Error creating requestBody")
sendPost(
diff --git a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalRequest.kt b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalRequest.kt
index adfd68924c..8239d7588b 100644
--- a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalRequest.kt
+++ b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalRequest.kt
@@ -68,6 +68,9 @@ import org.json.JSONException
* tokenization.
* @property riskCorrelationId A risk correlation ID created with Set Transaction Context on your
* server.
+ * @property enablePayPalAppSwitch Used to determine if the customer will use the PayPal app switch flow.
+ * Defaults to `false`.
+ * - Warning: This property is currently in beta and may change or be removed in future releases.
* @property userAuthenticationEmail User email to initiate a quicker authentication flow in cases
* where the user has a PayPal Account with the same email.
* @property userPhoneNumber User phone number used to initiate a quicker authentication flow in
@@ -85,6 +88,7 @@ abstract class PayPalRequest internal constructor(
open var displayName: String? = null,
open var merchantAccountId: String? = null,
open var riskCorrelationId: String? = null,
+ open var enablePayPalAppSwitch: Boolean = false,
open var userAuthenticationEmail: String? = null,
open var userPhoneNumber: PayPalPhoneNumber? = null,
open var lineItems: List = emptyList()
diff --git a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalVaultRequest.kt b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalVaultRequest.kt
index ea19bfb618..cb4493527a 100644
--- a/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalVaultRequest.kt
+++ b/PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalVaultRequest.kt
@@ -26,9 +26,6 @@ import org.json.JSONObject
* @property shouldOfferCredit Offers PayPal Credit if the customer qualifies. Defaults to false.
* @property recurringBillingDetails Optional: Recurring billing product details.
* @property recurringBillingPlanType Optional: Recurring billing plan type, or charge pattern.
- * @property enablePayPalAppSwitch Used to determine if the customer will use the PayPal app switch flow.
- * Defaults to `false`.
- * - Warning: This property is currently in beta and may change or be removed in future releases.
*/
@Parcelize
class PayPalVaultRequest
@@ -37,7 +34,7 @@ class PayPalVaultRequest
var shouldOfferCredit: Boolean = false,
var recurringBillingDetails: PayPalRecurringBillingDetails? = null,
var recurringBillingPlanType: PayPalRecurringBillingPlanType? = null,
- var enablePayPalAppSwitch: Boolean = false,
+ override var enablePayPalAppSwitch: Boolean = false,
override var localeCode: String? = null,
override var billingAgreementDescription: String? = null,
override var isShippingAddressRequired: Boolean = false,
@@ -61,6 +58,7 @@ class PayPalVaultRequest
displayName = displayName,
merchantAccountId = merchantAccountId,
riskCorrelationId = riskCorrelationId,
+ enablePayPalAppSwitch = enablePayPalAppSwitch,
userAuthenticationEmail = userAuthenticationEmail,
lineItems = lineItems
) {
diff --git a/PayPal/src/test/java/com/braintreepayments/api/paypal/PayPalCheckoutRequestUnitTest.java b/PayPal/src/test/java/com/braintreepayments/api/paypal/PayPalCheckoutRequestUnitTest.java
index 8752cde5f0..4a3e91952e 100644
--- a/PayPal/src/test/java/com/braintreepayments/api/paypal/PayPalCheckoutRequestUnitTest.java
+++ b/PayPal/src/test/java/com/braintreepayments/api/paypal/PayPalCheckoutRequestUnitTest.java
@@ -5,11 +5,12 @@
import com.braintreepayments.api.core.Authorization;
import com.braintreepayments.api.core.Configuration;
import com.braintreepayments.api.core.PostalAddress;
+import com.google.testing.junit.testparameterinjector.TestParameter;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RobolectricTestParameterInjector;
import java.util.ArrayList;
@@ -20,7 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
-@RunWith(RobolectricTestRunner.class)
+@RunWith(RobolectricTestParameterInjector.class)
public class PayPalCheckoutRequestUnitTest {
@Test
@@ -37,11 +38,13 @@ public void newPayPalCheckoutRequest_setsDefaultValues() {
assertNull(request.getLandingPageType());
assertNull(request.getBillingAgreementDescription());
assertFalse(request.getShouldOfferPayLater());
+ assertFalse(request.getEnablePayPalAppSwitch());
+ assertNull(request.getUserAuthenticationEmail());
assertFalse(request.getHasUserLocationConsent());
}
@Test
- public void setsValuesCorrectly() {
+ public void setsValuesCorrectly(@TestParameter boolean appSwitchEnabled) {
PostalAddress postalAddress = new PostalAddress();
PayPalCheckoutRequest request = new PayPalCheckoutRequest("1.00", true);
request.setCurrencyCode("USD");
@@ -56,6 +59,8 @@ public void setsValuesCorrectly() {
request.setUserAction(PayPalPaymentUserAction.USER_ACTION_COMMIT);
request.setDisplayName("Display Name");
request.setRiskCorrelationId("123-correlation");
+ request.setEnablePayPalAppSwitch(appSwitchEnabled);
+ request.setUserAuthenticationEmail("test-email");
request.setLandingPageType(PayPalLandingPageType.LANDING_PAGE_TYPE_LOGIN);
assertEquals("1.00", request.getAmount());
@@ -69,13 +74,15 @@ public void setsValuesCorrectly() {
assertEquals(PayPalPaymentUserAction.USER_ACTION_COMMIT, request.getUserAction());
assertEquals("Display Name", request.getDisplayName());
assertEquals("123-correlation", request.getRiskCorrelationId());
+ assertEquals(appSwitchEnabled, request.getEnablePayPalAppSwitch());
+ assertEquals("test-email", request.getUserAuthenticationEmail());
assertEquals(PayPalLandingPageType.LANDING_PAGE_TYPE_LOGIN, request.getLandingPageType());
assertTrue(request.getShouldOfferPayLater());
assertTrue(request.getHasUserLocationConsent());
}
@Test
- public void parcelsCorrectly() {
+ public void parcelsCorrectly(@TestParameter boolean appSwitchEnabled) {
PayPalCheckoutRequest request = new PayPalCheckoutRequest("12.34", true);
request.setCurrencyCode("USD");
request.setLocaleCode("en-US");
@@ -93,6 +100,8 @@ public void parcelsCorrectly() {
request.setDisplayName("Display Name");
request.setRiskCorrelationId("123-correlation");
request.setMerchantAccountId("merchant_account_id");
+ request.setEnablePayPalAppSwitch(appSwitchEnabled);
+ request.setUserAuthenticationEmail("test-email");
ArrayList lineItems = new ArrayList<>();
lineItems.add(new PayPalLineItem(PayPalLineItemKind.DEBIT, "An Item", "1", "1"));
@@ -110,6 +119,8 @@ public void parcelsCorrectly() {
result.getBillingAgreementDescription());
assertTrue(result.isShippingAddressRequired());
assertTrue(result.isShippingAddressEditable());
+ assertEquals(appSwitchEnabled, result.getEnablePayPalAppSwitch());
+ assertEquals("test-email", result.getUserAuthenticationEmail());
assertEquals("Postal Address", result.getShippingAddressOverride().getRecipientName());
assertEquals(PayPalPaymentIntent.SALE, result.getIntent());
assertEquals(PayPalLandingPageType.LANDING_PAGE_TYPE_LOGIN, result.getLandingPageType());
@@ -171,4 +182,5 @@ public void createRequestBody_sets_userPhoneNumber_when_not_null() throws JSONEx
assertTrue(requestBody.contains("\"phone_number\":{\"country_code\":\"1\",\"national_number\":\"1231231234\"}"));
}
+
}
\ No newline at end of file
diff --git a/PayPalMessaging/build.gradle b/PayPalMessaging/build.gradle
index a91188ccfe..6df18ade05 100644
--- a/PayPalMessaging/build.gradle
+++ b/PayPalMessaging/build.gradle
@@ -46,6 +46,7 @@ dependencies {
testImplementation libs.androidx.junit
testImplementation libs.mockk
testImplementation libs.mockito.core
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
}
diff --git a/SEPADirectDebit/build.gradle b/SEPADirectDebit/build.gradle
index 2925ab895f..450039518c 100644
--- a/SEPADirectDebit/build.gradle
+++ b/SEPADirectDebit/build.gradle
@@ -40,6 +40,7 @@ dependencies {
testImplementation libs.androidx.junit
testImplementation libs.mockito.core
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
androidTestImplementation project(':TestUtils')
diff --git a/SharedUtils/build.gradle b/SharedUtils/build.gradle
index 5688d60762..69760b7ba5 100644
--- a/SharedUtils/build.gradle
+++ b/SharedUtils/build.gradle
@@ -40,6 +40,7 @@ dependencies {
testImplementation libs.androidx.test.core
testImplementation libs.mockito.core
testImplementation libs.robolectric
+ testImplementation libs.test.parameter.injector
androidTestImplementation libs.androidx.test.runner
androidTestImplementation libs.androidx.junit
diff --git a/ShopperInsights/build.gradle b/ShopperInsights/build.gradle
index f624ad77d9..f26f166142 100644
--- a/ShopperInsights/build.gradle
+++ b/ShopperInsights/build.gradle
@@ -40,6 +40,7 @@ dependencies {
testImplementation libs.mockk
testImplementation libs.robolectric
testImplementation libs.androidx.junit
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
}
diff --git a/TestUtils/build.gradle b/TestUtils/build.gradle
index 0c5721dc8b..07c5aeaa86 100644
--- a/TestUtils/build.gradle
+++ b/TestUtils/build.gradle
@@ -37,6 +37,7 @@ dependencies {
compileOnly libs.json.assert
compileOnly libs.mockk
compileOnly libs.mockito.core
+ compileOnly libs.test.parameter.injector
implementation project(':BraintreeCore')
implementation project(':ThreeDSecure')
diff --git a/ThreeDSecure/build.gradle b/ThreeDSecure/build.gradle
index 2329ce8fef..0aadacb43c 100644
--- a/ThreeDSecure/build.gradle
+++ b/ThreeDSecure/build.gradle
@@ -45,6 +45,7 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.json.assert
testImplementation libs.androidx.test.core
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
androidTestImplementation libs.androidx.test.rules
diff --git a/Venmo/build.gradle b/Venmo/build.gradle
index 67f374c627..e91e1a0581 100644
--- a/Venmo/build.gradle
+++ b/Venmo/build.gradle
@@ -44,6 +44,7 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.json.assert
testImplementation libs.mockk
+ testImplementation libs.test.parameter.injector
testImplementation project(':TestUtils')
}
diff --git a/VisaCheckout/build.gradle b/VisaCheckout/build.gradle
index a7912f8b9a..897574df77 100644
--- a/VisaCheckout/build.gradle
+++ b/VisaCheckout/build.gradle
@@ -49,6 +49,7 @@ dependencies {
testImplementation libs.mockk
testImplementation libs.mockito.core
testImplementation libs.json.assert
+ testImplementation libs.test.parameter.injector
}
/* maven deploy + signing */
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3e00b4b217..700f7725e6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -19,7 +19,8 @@ cardinal = "2.2.7-5"
navigationSafeArgsGradlePlugin = "2.5.0"
playServices = "19.4.0"
junit = "4.13.2"
-robolectric = "4.11.1"
+testParameterInjector = "1.18"
+robolectric = "4.14-beta-1"
mockito = "5.7.0"
jsonAssert = "1.5.1"
mockk = "1.13.8"
@@ -73,6 +74,7 @@ robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
json-assert = { group = "org.skyscreamer", name = "jsonassert", version.ref = "jsonAssert" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
+test-parameter-injector = { group = "com.google.testparameterinjector", name = "test-parameter-injector", version.ref = "testParameterInjector" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
detekt-rules-libraries = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }