diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8956a9531..6980096c19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Braintree Android SDK Release Notes
+## unreleased
+
+* GooglePay
+ * Remove requirement for `GooglePayActivity` to be declared in the Android manifest (fixes #1572)
+ * Note: If upgrading from v4, any manual `GooglePayActivity` declaration in your app's manifest should be removed
+ * Deprecate unused `GooglePayClient.EXTRA_ENVIRONMENT` and `GooglePayClient.EXTRA_PAYMENT_DATA_REQUEST` constants
+
## 5.28.1 (2026-06-01)
* PayPal
diff --git a/GooglePay/src/main/AndroidManifest.xml b/GooglePay/src/main/AndroidManifest.xml
index 3cb6a1e553..6537ec12ba 100644
--- a/GooglePay/src/main/AndroidManifest.xml
+++ b/GooglePay/src/main/AndroidManifest.xml
@@ -1,7 +1,5 @@
-
-
+
\ No newline at end of file
diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.kt
deleted file mode 100644
index 5df2a0202e..0000000000
--- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.braintreepayments.api.googlepay
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import com.braintreepayments.api.sharedutils.IntentExtensions.parcelable
-import com.google.android.gms.wallet.AutoResolveHelper
-import com.google.android.gms.wallet.PaymentDataRequest
-import com.google.android.gms.wallet.Wallet
-import com.google.android.gms.wallet.Wallet.WalletOptions
-import com.google.android.gms.wallet.WalletConstants
-
-internal class GooglePayActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_RECREATING)) {
- return
- }
-
- val paymentsClient = Wallet.getPaymentsClient(
- this, WalletOptions.Builder()
- .setEnvironment(
- intent.getIntExtra(
- EXTRA_ENVIRONMENT,
- WalletConstants.ENVIRONMENT_TEST
- )
- )
- .build()
- )
-
- val request = intent.parcelable(EXTRA_PAYMENT_DATA_REQUEST)
- if (request != null) {
- AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(request), this, REQUEST_CODE)
- }
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putBoolean(EXTRA_RECREATING, true)
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
-
- setResult(resultCode, data)
- finish()
- }
-
- override fun finish() {
- super.finish()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, android.R.anim.fade_in, android.R.anim.fade_out)
- } else {
- @Suppress("DEPRECATION")
- overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
- }
- }
-
- companion object {
- const val EXTRA_ENVIRONMENT: String =
- "com.braintreepayments.api.EXTRA_ENVIRONMENT"
- const val EXTRA_PAYMENT_DATA_REQUEST: String =
- "com.braintreepayments.api.EXTRA_PAYMENT_DATA_REQUEST"
-
- private const val EXTRA_RECREATING = "com.braintreepayments.api.EXTRA_RECREATING"
-
- private const val REQUEST_CODE = 1
- }
-}
diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt
deleted file mode 100644
index ee1f2e8fba..0000000000
--- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.braintreepayments.api.googlepay
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import androidx.activity.result.contract.ActivityResultContract
-import com.braintreepayments.api.core.BraintreeException
-import com.braintreepayments.api.core.UserCanceledException
-import com.google.android.gms.wallet.AutoResolveHelper
-import com.google.android.gms.wallet.PaymentData
-
-internal class GooglePayActivityResultContract :
- ActivityResultContract() {
- override fun createIntent(context: Context, input: GooglePayPaymentAuthRequestParams): Intent {
- return Intent(context, GooglePayActivity::class.java)
- .putExtra(GooglePayClient.EXTRA_ENVIRONMENT, input.googlePayEnvironment)
- .putExtra(GooglePayClient.EXTRA_PAYMENT_DATA_REQUEST, input.paymentDataRequest)
- }
-
- override fun parseResult(resultCode: Int, intent: Intent?): GooglePayPaymentAuthResult {
- when (resultCode) {
- Activity.RESULT_OK -> {
- if (intent != null) {
- return GooglePayPaymentAuthResult(PaymentData.getFromIntent(intent), null)
- }
- }
- Activity.RESULT_CANCELED -> {
- return GooglePayPaymentAuthResult(
- null,
- UserCanceledException("User canceled Google Pay.")
- )
- }
- AutoResolveHelper.RESULT_ERROR -> {
- if (intent != null) {
- return GooglePayPaymentAuthResult(
- null,
- GooglePayException(
- "An error was encountered during the Google Pay " +
- "flow. See the status object in this exception for more details.",
- AutoResolveHelper.getStatusFromIntent(intent)
- )
- )
- }
- }
- }
-
- return GooglePayPaymentAuthResult(null, BraintreeException("An unexpected error occurred."))
- }
-}
diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt
index 0863bce870..52346d7f8c 100644
--- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt
+++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt
@@ -207,15 +207,6 @@ class GooglePayClient internal constructor(
analyticsParamRepository.reset()
braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_STARTED)
- if (!validateManifest()) {
- return GooglePayPaymentAuthRequest.Failure(
- BraintreeException(
- "GooglePayActivity was not found in the Android " +
- "manifest, or did not have a theme of R.style.bt_transparent_activity"
- )
- )
- }
-
try {
val configuration = braintreeClient.getConfiguration()
if (configuration.isGooglePayEnabled) {
@@ -584,13 +575,6 @@ class GooglePayClient internal constructor(
request.setEnvironment(configuration.googlePayEnvironment)
}
- private fun validateManifest(): Boolean {
- val activityInfo =
- braintreeClient.getManifestActivityInfo(GooglePayActivity::class.java)
- return activityInfo != null &&
- activityInfo.themeResource == R.style.bt_transparent_activity
- }
-
private fun callbackPaymentRequestSuccess(
request: GooglePayPaymentAuthRequest.ReadyToLaunch,
callback: GooglePayPaymentAuthRequestCallback
@@ -635,7 +619,12 @@ class GooglePayClient internal constructor(
}
companion object {
+ // NEXT_MAJOR_VERSION: Remove the following constants that are no longer used in the Google Pay flow
+ @Deprecated("This constant is no longer used in the Google Pay flow and will be removed in a future release.")
const val EXTRA_ENVIRONMENT: String = "com.braintreepayments.api.EXTRA_ENVIRONMENT"
+
+ // NEXT_MAJOR_VERSION: Remove the following constants that are no longer used in the Google Pay flow
+ @Deprecated("This constant is no longer used in the Google Pay flow and will be removed in a future release.")
const val EXTRA_PAYMENT_DATA_REQUEST: String =
"com.braintreepayments.api.EXTRA_PAYMENT_DATA_REQUEST"
diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt
index 69290c2515..6a8ae35b58 100644
--- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt
+++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt
@@ -6,6 +6,7 @@ import com.braintreepayments.api.googlepay.GooglePayReadinessResult.NotReadyToPa
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import com.google.android.gms.wallet.IsReadyToPayRequest
+import com.google.android.gms.wallet.PaymentData
import com.google.android.gms.wallet.Wallet
import com.google.android.gms.wallet.Wallet.WalletOptions
import com.google.android.gms.wallet.WalletConstants
@@ -41,6 +42,14 @@ internal class GooglePayInternalClient {
}
}
+ fun loadPaymentData(context: Context, params: GooglePayPaymentAuthRequestParams): Task {
+ val paymentsClient = Wallet.getPaymentsClient(
+ context,
+ WalletOptions.Builder().setEnvironment(params.googlePayEnvironment).build()
+ )
+ return paymentsClient.loadPaymentData(params.paymentDataRequest)
+ }
+
private fun getGooglePayEnvironment(configuration: Configuration): Int {
return if ("production" == configuration.googlePayEnvironment) {
WalletConstants.ENVIRONMENT_PRODUCTION
diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt
index 32f321b77c..32e5f33b34 100644
--- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt
+++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt
@@ -1,10 +1,16 @@
package com.braintreepayments.api.googlepay
+import android.content.Context
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
+import com.braintreepayments.api.core.UserCanceledException
+import com.google.android.gms.tasks.Task
+import com.google.android.gms.wallet.PaymentData
+import com.google.android.gms.wallet.contract.ApiTaskResult
+import com.google.android.gms.wallet.contract.TaskResultContracts
/**
* Responsible for launching the Google Pay payment sheet
@@ -12,16 +18,33 @@ import androidx.lifecycle.LifecycleOwner
class GooglePayLauncher internal constructor(
registry: ActivityResultRegistry,
lifecycleOwner: LifecycleOwner,
+ context: Context,
+ private val internalGooglePayClient: GooglePayInternalClient = GooglePayInternalClient(),
callback: GooglePayLauncherCallback
) {
- private val activityLauncher: ActivityResultLauncher = registry.register(
+ private val appContext: Context = context.applicationContext
+
+ private val activityLauncher: ActivityResultLauncher> = registry.register(
GOOGLE_PAY_RESULT, lifecycleOwner,
- GooglePayActivityResultContract()
- ) { googlePayPaymentAuthResult: GooglePayPaymentAuthResult ->
- callback.onGooglePayLauncherResult(
- googlePayPaymentAuthResult
- )
+ TaskResultContracts.GetPaymentDataResult()
+ ) { apiTaskResult: ApiTaskResult ->
+ val result = when {
+ apiTaskResult.status.isSuccess ->
+ GooglePayPaymentAuthResult(apiTaskResult.result, null)
+ apiTaskResult.status.isCanceled ->
+ GooglePayPaymentAuthResult(null, UserCanceledException("User canceled Google Pay."))
+ else ->
+ GooglePayPaymentAuthResult(
+ null,
+ GooglePayException(
+ "An error was encountered during the Google Pay " +
+ "flow. See the status object in this exception for more details.",
+ apiTaskResult.status
+ )
+ )
+ }
+ callback.onGooglePayLauncherResult(result)
}
/**
@@ -36,8 +59,10 @@ class GooglePayLauncher internal constructor(
fragment: Fragment,
callback: GooglePayLauncherCallback
) : this(
- fragment.requireActivity().activityResultRegistry, fragment.viewLifecycleOwner,
- callback
+ fragment.requireActivity().activityResultRegistry,
+ fragment.viewLifecycleOwner,
+ fragment.requireContext(),
+ callback = callback
)
/**
@@ -51,7 +76,7 @@ class GooglePayLauncher internal constructor(
constructor(
activity: ComponentActivity,
callback: GooglePayLauncherCallback
- ) : this(activity.activityResultRegistry, activity, callback)
+ ) : this(activity.activityResultRegistry, activity, activity, callback = callback)
/**
* Launches the Google Pay payment sheet. This method cannot be called until the lifecycle of
@@ -62,7 +87,10 @@ class GooglePayLauncher internal constructor(
* received from invoking [GooglePayClient.createPaymentAuthRequest]
*/
fun launch(paymentAuthRequest: GooglePayPaymentAuthRequest.ReadyToLaunch) {
- activityLauncher.launch(paymentAuthRequest.requestParams)
+ internalGooglePayClient.loadPaymentData(appContext, paymentAuthRequest.requestParams)
+ .addOnCompleteListener { completedTask ->
+ activityLauncher.launch(completedTask)
+ }
}
companion object {
diff --git a/GooglePay/src/main/res/values/styles.xml b/GooglePay/src/main/res/values/styles.xml
deleted file mode 100644
index f62686308f..0000000000
--- a/GooglePay/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContractUnitTest.kt b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContractUnitTest.kt
deleted file mode 100644
index 549a42e726..0000000000
--- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContractUnitTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.braintreepayments.api.googlepay
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import androidx.test.core.app.ApplicationProvider
-import com.braintreepayments.api.core.BraintreeException
-import com.braintreepayments.api.core.UserCanceledException
-import com.braintreepayments.api.sharedutils.IntentExtensions.parcelable
-import com.google.android.gms.wallet.AutoResolveHelper
-import com.google.android.gms.wallet.PaymentData
-import com.google.android.gms.wallet.PaymentDataRequest
-import junit.framework.TestCase.assertEquals
-import junit.framework.TestCase.assertNotNull
-import junit.framework.TestCase.assertNull
-import junit.framework.TestCase.assertSame
-import junit.framework.TestCase.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-
-@RunWith(RobolectricTestRunner::class)
-class GooglePayActivityResultContractUnitTest {
-
- @Test
- fun `createIntent returns Intent with extras`() {
- val googlePayRequest = GooglePayRequest("USD", "1.00", GooglePayTotalPriceStatus.TOTAL_PRICE_STATUS_FINAL)
-
- val paymentDataRequest = PaymentDataRequest.fromJson(googlePayRequest.toJson())
-
- val intentData = GooglePayPaymentAuthRequestParams(1, paymentDataRequest)
-
- val context = ApplicationProvider.getApplicationContext()
-
- val sut = GooglePayActivityResultContract()
- val intent = sut.createIntent(context, intentData)
-
- assertEquals(1, intent.getIntExtra(GooglePayClient.EXTRA_ENVIRONMENT, 0))
- assertSame(
- paymentDataRequest,
- intent.parcelable(GooglePayClient.EXTRA_PAYMENT_DATA_REQUEST)
- )
- }
-
- @Test
- fun `parseResult when result is OK and payment data exists returns Google Pay result with PaymentData`() {
- val paymentData = PaymentData.fromJson("{}")
- val data = Intent()
- paymentData.putIntoIntent(data)
-
- val sut = GooglePayActivityResultContract()
-
- val result = sut.parseResult(Activity.RESULT_OK, data)
- assertNotNull(result.paymentData)
- assertNull(result.error)
- }
-
- @Test
- fun `parseResult when result is canceled returns Google Pay result with error`() {
- val data = Intent()
-
- val sut = GooglePayActivityResultContract()
-
- val result = sut.parseResult(Activity.RESULT_CANCELED, data)
-
- val error = result.error
- assertTrue(error is UserCanceledException)
- assertEquals("User canceled Google Pay.", error!!.message)
- assertNull(result.paymentData)
- }
-
- @Test
- fun `parseResult when RESULT_ERROR returns Google Pay result with error`() {
- val data = Intent()
-
- val sut = GooglePayActivityResultContract()
-
- val result = sut.parseResult(AutoResolveHelper.RESULT_ERROR, data)
-
- val error = result.error
- assertTrue(error is GooglePayException)
- assertEquals(
- "An error was encountered during the Google Pay " +
- "flow. See the status object in this exception for more details.", error!!.message
- )
- assertNull(result.paymentData)
- }
-
- @Test
- fun `parseResult when result is unexpected returns Google Pay result with error`() {
- val data = Intent()
-
- val sut = GooglePayActivityResultContract()
-
- val result = sut.parseResult(2, data)
-
- val error = result.error
- assertTrue(error is BraintreeException)
- assertEquals("An unexpected error occurred.", error!!.message)
- assertNull(result.paymentData)
- }
-}
diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.kt b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.kt
index 8cd31f7179..92124fab5e 100644
--- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.kt
+++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.kt
@@ -1,6 +1,5 @@
package com.braintreepayments.api.googlepay
-import android.content.pm.ActivityInfo
import androidx.fragment.app.FragmentActivity
import com.braintreepayments.api.core.AnalyticsEventParams
import com.braintreepayments.api.core.AnalyticsParamRepository
@@ -53,7 +52,6 @@ class GooglePayClientUnitTest {
private lateinit var readyToPayCallback: GooglePayIsReadyToPayCallback
private lateinit var intentDataCallback: GooglePayPaymentAuthRequestCallback
private lateinit var activityResultCallback: GooglePayTokenizeCallback
- private lateinit var activityInfo: ActivityInfo
private lateinit var analyticsParamRepository: AnalyticsParamRepository
private lateinit var merchantRepository: MerchantRepository
private lateinit var testScope: TestScope
@@ -64,13 +62,11 @@ class GooglePayClientUnitTest {
readyToPayCallback = mockk(relaxed = true)
activityResultCallback = mockk(relaxed = true)
intentDataCallback = mockk(relaxed = true)
- activityInfo = mockk(relaxed = true)
analyticsParamRepository = mockk(relaxed = true)
merchantRepository = mockk(relaxed = true)
testScope = TestScope(testDispatcher)
baseRequest = GooglePayRequest("USD", "1.00", GooglePayTotalPriceStatus.TOTAL_PRICE_STATUS_FINAL)
- every { activityInfo.themeResource } returns R.style.bt_transparent_activity
}
@Test
@@ -177,7 +173,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.TOKENIZATION_KEY)
@@ -245,7 +240,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.TOKENIZATION_KEY)
@@ -287,7 +281,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every {
@@ -427,7 +420,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.TOKENIZATION_KEY)
@@ -479,7 +471,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.BASE64_CLIENT_TOKEN)
@@ -532,7 +523,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -564,7 +554,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.BASE64_CLIENT_TOKEN)
@@ -617,7 +606,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -665,7 +653,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -713,7 +700,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -762,7 +748,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -810,7 +795,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -862,7 +846,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -916,7 +899,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -971,7 +953,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1036,7 +1017,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1087,7 +1067,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1126,54 +1105,6 @@ class GooglePayClientUnitTest {
assertEquals("ELO_DEBIT", allowedCardNetworks.getString(1))
}
- @Test
- fun createPaymentAuthRequest_whenManifestInvalid_forwardsExceptionToListener() = runTest(testDispatcher) {
- val configuration = Configuration.fromJson(TestConfigurationBuilder()
- .googlePay(
- TestConfigurationBuilder.TestGooglePayConfigurationBuilder()
- .environment("sandbox")
- .googleAuthorizationFingerprint("google-auth-fingerprint")
- .paypalClientId("paypal-client-id-for-google-payment")
- .supportedNetworks(arrayOf("visa", "mastercard", "amex", "discover"))
- .enabled(true)
- )
- .withAnalytics()
- .build())
-
- val braintreeClient = MockkBraintreeClientBuilder()
- .configurationSuccess(configuration)
- .build()
-
- every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
-
- val internalGooglePayClient = MockkGooglePayInternalClientBuilder().build()
-
- val sut = GooglePayClient(
- braintreeClient,
- internalGooglePayClient,
- analyticsParamRepository,
- merchantRepository,
- testDispatcher,
- testScope
- )
- sut.createPaymentAuthRequest(baseRequest, intentDataCallback)
- advanceUntilIdle()
-
- val captor = slot()
- verify { intentDataCallback.onGooglePayPaymentAuthRequest(capture(captor)) }
-
- val request = captor.captured
- assertTrue(request is GooglePayPaymentAuthRequest.Failure)
- val exception = request.error
- assertTrue(exception is BraintreeException)
- assertEquals(
- "GooglePayActivity was not found in the Android manifest, " +
- "or did not have a theme of R.style.bt_transparent_activity",
- exception.message
- )
- verify { braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED, any()) }
- }
-
@Test
fun tokenize_withCardToken_returnsGooglePayNonce() {
val paymentDataJson = Fixtures.RESPONSE_GOOGLE_PAY_CARD
@@ -1192,7 +1123,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1235,7 +1165,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1359,7 +1288,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString("sandbox_tokenization_string")
@@ -1395,7 +1323,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
val authorization = Authorization.fromString(Fixtures.TOKENIZATION_KEY)
@@ -1437,7 +1364,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
val authorization = Authorization.fromString(Fixtures.BASE64_CLIENT_TOKEN)
@@ -1468,7 +1394,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
val authorization = Authorization.fromString(Fixtures.TOKENIZATION_KEY)
@@ -1523,7 +1448,6 @@ class GooglePayClientUnitTest {
val braintreeClient = MockkBraintreeClientBuilder()
.configurationSuccess(configuration)
- .activityInfo(activityInfo)
.build()
every { merchantRepository.authorization } returns Authorization.fromString(Fixtures.TOKENIZATION_KEY)
diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.kt b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.kt
index c5a7944b50..f57d35c558 100644
--- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.kt
+++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.kt
@@ -1,13 +1,26 @@
package com.braintreepayments.api.googlepay
+import android.content.Context
+import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContract
import androidx.fragment.app.FragmentActivity
+import androidx.test.core.app.ApplicationProvider
+import com.braintreepayments.api.core.UserCanceledException
+import com.google.android.gms.common.api.Status
+import com.google.android.gms.tasks.Task
+import com.google.android.gms.wallet.PaymentData
import com.google.android.gms.wallet.PaymentDataRequest
+import com.google.android.gms.wallet.contract.ApiTaskResult
+import com.google.android.gms.wallet.contract.TaskResultContracts
import io.mockk.every
import io.mockk.mockk
+import io.mockk.slot
import io.mockk.verify
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -16,8 +29,7 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class GooglePayLauncherUnitTest {
- private val activityResultLauncher =
- mockk>(relaxed = true)
+ private val activityResultLauncher = mockk>>(relaxed = true)
private val callback = mockk(relaxed = true)
private val activityResultRegistry = mockk(relaxed = true)
@@ -27,7 +39,7 @@ class GooglePayLauncherUnitTest {
activityResultRegistry.register(
any(),
any(),
- any>(),
+ any, Any>>(),
any()
)
} returns activityResultLauncher
@@ -37,25 +49,31 @@ class GooglePayLauncherUnitTest {
fun constructor_createsActivityLauncher() {
val expectedKey = "com.braintreepayments.api.GooglePay.RESULT"
val lifecycleOwner = FragmentActivity()
+ val context = ApplicationProvider.getApplicationContext()
val registry = mockk(relaxed = true)
- GooglePayLauncher(registry, lifecycleOwner, callback)
+ GooglePayLauncher(registry, lifecycleOwner, context, callback = callback)
verify {
registry.register(
eq(expectedKey), eq(lifecycleOwner),
- any>(),
+ any(),
any()
)
}
}
@Test
- fun launch_launchesActivity() {
+ fun launch_launchesTask() {
val lifecycleOwner = FragmentActivity()
+ val context = ApplicationProvider.getApplicationContext()
+ val mockTask = mockk>(relaxed = true)
+ val internalGooglePayClient = MockkGooglePayInternalClientBuilder()
+ .loadPaymentDataTask(mockTask)
+ .build()
+
val sut = GooglePayLauncher(
- activityResultRegistry, lifecycleOwner,
- callback
+ activityResultRegistry, lifecycleOwner, context, internalGooglePayClient, callback
)
val googlePayRequest = GooglePayRequest("USD", "1.00", GooglePayTotalPriceStatus.TOTAL_PRICE_STATUS_FINAL)
@@ -63,6 +81,88 @@ class GooglePayLauncherUnitTest {
val intentData = GooglePayPaymentAuthRequestParams(1, paymentDataRequest)
sut.launch(GooglePayPaymentAuthRequest.ReadyToLaunch(intentData))
- verify { activityResultLauncher.launch(intentData) }
+ verify { activityResultLauncher.launch(mockTask) }
+ }
+
+ @Test
+ fun `callback_whenSuccess_passesPaymentDataToCallback`() {
+ val callbackSlot = slot>>()
+ val registry = mockk(relaxed = true)
+ every {
+ registry.register(any(), any(), any(), capture(callbackSlot))
+ } returns mockk(relaxed = true)
+ val lifecycleOwner = FragmentActivity()
+ val context = ApplicationProvider.getApplicationContext()
+ GooglePayLauncher(registry, lifecycleOwner, context, callback = callback)
+
+ val paymentData = mockk(relaxed = true)
+ val status = mockk(relaxed = true)
+ every { status.isSuccess } returns true
+ val apiTaskResult = mockk>(relaxed = true)
+ every { apiTaskResult.status } returns status
+ every { apiTaskResult.result } returns paymentData
+
+ callbackSlot.captured.onActivityResult(apiTaskResult)
+
+ val resultSlot = slot()
+ verify { callback.onGooglePayLauncherResult(capture(resultSlot)) }
+ assertEquals(paymentData, resultSlot.captured.paymentData)
+ assertNull(resultSlot.captured.error)
+ }
+
+ @Test
+ fun `callback_whenCanceled_passesUserCanceledExceptionToCallback`() {
+ val callbackSlot = slot>>()
+ val registry = mockk(relaxed = true)
+ every {
+ registry.register(any(), any(), any(), capture(callbackSlot))
+ } returns mockk(relaxed = true)
+ val lifecycleOwner = FragmentActivity()
+ val context = ApplicationProvider.getApplicationContext()
+ GooglePayLauncher(registry, lifecycleOwner, context, callback = callback)
+
+ val status = mockk(relaxed = true)
+ every { status.isSuccess } returns false
+ every { status.isCanceled } returns true
+ val apiTaskResult = mockk>(relaxed = true)
+ every { apiTaskResult.status } returns status
+
+ callbackSlot.captured.onActivityResult(apiTaskResult)
+
+ val resultSlot = slot()
+ verify { callback.onGooglePayLauncherResult(capture(resultSlot)) }
+ assertNull(resultSlot.captured.paymentData)
+ assertTrue(resultSlot.captured.error is UserCanceledException)
+ assertEquals("User canceled Google Pay.", resultSlot.captured.error!!.message)
+ }
+
+ @Test
+ fun `callback_whenError_passesGooglePayExceptionToCallback`() {
+ val callbackSlot = slot>>()
+ val registry = mockk(relaxed = true)
+ every {
+ registry.register(any(), any(), any(), capture(callbackSlot))
+ } returns mockk(relaxed = true)
+ val lifecycleOwner = FragmentActivity()
+ val context = ApplicationProvider.getApplicationContext()
+ GooglePayLauncher(registry, lifecycleOwner, context, callback = callback)
+
+ val status = mockk(relaxed = true)
+ every { status.isSuccess } returns false
+ every { status.isCanceled } returns false
+ val apiTaskResult = mockk>(relaxed = true)
+ every { apiTaskResult.status } returns status
+
+ callbackSlot.captured.onActivityResult(apiTaskResult)
+
+ val resultSlot = slot()
+ verify { callback.onGooglePayLauncherResult(capture(resultSlot)) }
+ assertNull(resultSlot.captured.paymentData)
+ assertTrue(resultSlot.captured.error is GooglePayException)
+ assertEquals(
+ "An error was encountered during the Google Pay " +
+ "flow. See the status object in this exception for more details.",
+ resultSlot.captured.error!!.message
+ )
}
}
diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/MockkGooglePayInternalClientBuilder.kt b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/MockkGooglePayInternalClientBuilder.kt
index 2121af9632..8d211ea815 100644
--- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/MockkGooglePayInternalClientBuilder.kt
+++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/MockkGooglePayInternalClientBuilder.kt
@@ -1,9 +1,14 @@
package com.braintreepayments.api.googlepay
+import android.content.Context
import androidx.fragment.app.FragmentActivity
import com.braintreepayments.api.core.Configuration
+import com.google.android.gms.tasks.OnCompleteListener
+import com.google.android.gms.tasks.Task
import com.google.android.gms.wallet.IsReadyToPayRequest
+import com.google.android.gms.wallet.PaymentData
import io.mockk.coEvery
+import io.mockk.every
import io.mockk.mockk
@Suppress("MagicNumber")
@@ -11,6 +16,7 @@ internal class MockkGooglePayInternalClientBuilder {
private var isReadyToPay: Boolean = false
private var isReadyToPayError: Exception? = null
+ private var loadPaymentDataTask: Task = buildCompletingTask(mockk(relaxed = true))
fun isReadyToPay(isReadyToPay: Boolean): MockkGooglePayInternalClientBuilder {
this.isReadyToPay = isReadyToPay
@@ -22,6 +28,11 @@ internal class MockkGooglePayInternalClientBuilder {
return this
}
+ fun loadPaymentDataTask(task: Task): MockkGooglePayInternalClientBuilder {
+ this.loadPaymentDataTask = buildCompletingTask(task)
+ return this
+ }
+
fun build(): GooglePayInternalClient {
val googlePayInternalClient = mockk(relaxed = true)
@@ -39,6 +50,23 @@ internal class MockkGooglePayInternalClientBuilder {
?: let { GooglePayReadinessResult.ReadyToPay }
}
+ every {
+ googlePayInternalClient.loadPaymentData(
+ any(),
+ any()
+ )
+ } returns loadPaymentDataTask
+
return googlePayInternalClient
}
+
+ companion object {
+ fun buildCompletingTask(task: Task): Task {
+ every { task.addOnCompleteListener(any()) } answers {
+ firstArg>().onComplete(task)
+ task
+ }
+ return task
+ }
+ }
}