Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 1 addition & 3 deletions GooglePay/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true"/>
<activity android:name=".GooglePayActivity"
android:theme="@style/bt_transparent_activity"/>
</application>
</manifest>
</manifest>

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,6 +42,14 @@ internal class GooglePayInternalClient {
}
}

fun loadPaymentData(context: Context, params: GooglePayPaymentAuthRequestParams): Task<PaymentData> {
Comment thread
saralvasquez marked this conversation as resolved.
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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,50 @@
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
*/
class GooglePayLauncher internal constructor(
registry: ActivityResultRegistry,
lifecycleOwner: LifecycleOwner,
context: Context,
private val internalGooglePayClient: GooglePayInternalClient = GooglePayInternalClient(),
callback: GooglePayLauncherCallback
) {

private val activityLauncher: ActivityResultLauncher<GooglePayPaymentAuthRequestParams> = registry.register(
private val appContext: Context = context.applicationContext

private val activityLauncher: ActivityResultLauncher<Task<PaymentData>> = registry.register(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this is the crux of the change. Are the any implications to older APIs or is this fully backwards compatible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. I tested with older versions of the APIs on emulator and it worked just fine but I'll make sure to test on all supported API versions to make sure we won't run into issues

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I couldn't get some of the older emulators to work well enough to test, but we do have confirmation that the new API is backwards compatible back to versions that are so old we don't even support. So I think we should be good

GOOGLE_PAY_RESULT, lifecycleOwner,
GooglePayActivityResultContract()
) { googlePayPaymentAuthResult: GooglePayPaymentAuthResult ->
callback.onGooglePayLauncherResult(
googlePayPaymentAuthResult
)
TaskResultContracts.GetPaymentDataResult()
) { apiTaskResult: ApiTaskResult<PaymentData> ->
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 " +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way we can display the status object in our demo app, so that we can see what caused an error right away?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually already covered in the demo app. This PR doesn't change how results are getting delivered, it only changes what's delivering them. If you take a look at the delete results contract file you can see that the handling of the payment status and sending of GooglePayPaymentAuthResult are very similar to how it looks in the Launcher now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much, I can see it now!

"flow. See the status object in this exception for more details.",
apiTaskResult.status
)
)
}
callback.onGooglePayLauncherResult(result)
}

/**
Expand All @@ -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
)

/**
Expand All @@ -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
Expand All @@ -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 {
Expand Down
16 changes: 0 additions & 16 deletions GooglePay/src/main/res/values/styles.xml

This file was deleted.

Loading
Loading