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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* 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
* Update Google Pay dependency (play-services-wallet) to version 19.5.0

## 5.28.1 (2026-06-01)

Expand Down
1 change: 1 addition & 0 deletions Demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dependencies {
def composeBom = platform(libs.androidx.compose.bom)
implementation composeBom
implementation libs.androidx.material3
implementation libs.google.pay.button

debugImplementation libs.leakcanary

Expand Down
121 changes: 0 additions & 121 deletions Demo/src/main/java/com/braintreepayments/demo/GooglePayFragment.java

This file was deleted.

176 changes: 176 additions & 0 deletions Demo/src/main/java/com/braintreepayments/demo/GooglePayFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.braintreepayments.demo

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.braintreepayments.api.core.PaymentMethodNonce
import com.braintreepayments.api.core.UserCanceledException
import com.braintreepayments.api.googlepay.GooglePayBillingAddressFormat
import com.braintreepayments.api.googlepay.GooglePayCheckoutOption
import com.braintreepayments.api.googlepay.GooglePayClient
import com.braintreepayments.api.googlepay.GooglePayLauncher
import com.braintreepayments.api.googlepay.GooglePayPaymentAuthRequest
import com.braintreepayments.api.googlepay.GooglePayReadinessResult
import com.braintreepayments.api.googlepay.GooglePayRequest
import com.braintreepayments.api.googlepay.GooglePayResult
import com.braintreepayments.api.googlepay.GooglePayShippingAddressParameters
import com.braintreepayments.api.googlepay.GooglePayTotalPriceStatus
import com.google.pay.button.ButtonType
import com.google.pay.button.PayButton
import androidx.compose.foundation.layout.Column
import org.json.JSONArray
import org.json.JSONObject

class GooglePayFragment : BaseFragment() {

private lateinit var googlePayClient: GooglePayClient
private lateinit var googlePayLauncher: GooglePayLauncher

private val args: GooglePayFragmentArgs by navArgs()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
googlePayClient = GooglePayClient(requireContext(), args.authString)
googlePayLauncher = GooglePayLauncher(this) { paymentAuthResult ->
googlePayClient.tokenize(paymentAuthResult) { googlePayResult ->
when (googlePayResult) {
is GooglePayResult.Failure -> handleError(googlePayResult.error)
is GooglePayResult.Success -> handleGooglePayActivityResult(googlePayResult.nonce)
is GooglePayResult.Cancel -> handleError(UserCanceledException("User canceled Google Pay"))
}
}
}

return ComposeView(requireContext()).apply {
setContent {
GooglePayScreen()
}
}
}

@Composable
fun GooglePayScreen() {
var isReadyToPay by remember { mutableStateOf(false) }
var allowedPaymentMethods by remember { mutableStateOf("") }

LaunchedEffect(Unit) {
googlePayClient.isReadyToPay(requireActivity()) { result ->
if (result is GooglePayReadinessResult.ReadyToPay) {
isReadyToPay = true
// Note: We avoid createPaymentAuthRequest here to prevent unnecessary analytics noise.
// Instead, we assume common card networks are supported for the demo.
val cardParams = JSONObject()
.put(
"allowedAuthMethods",
JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS")
)
.put(
"allowedCardNetworks",
JSONArray().put("VISA").put("MASTERCARD").put("AMEX").put("DISCOVER")
.put("JCB")
)

allowedPaymentMethods = JSONArray().put(
JSONObject().put("type", "CARD").put("parameters", cardParams)
).toString()
} else {
showDialog(
"Google Pay is not available. The following issues could be the cause:\n\n" +
"No user is logged in to the device.\n\n" +
"Google Play Services is missing or out of date."
)
}
}
}

Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (isReadyToPay) {
if (allowedPaymentMethods.isNotEmpty()) {
PayButton(
modifier = Modifier.width(280.dp),
onClick = { launchGooglePay() },
allowedPaymentMethods = allowedPaymentMethods,
type = ButtonType.Buy
)
} else {
LaunchedEffect(Unit) {
showDialog(
"allowedPaymentMethods cannot be empty.\n\n" +
"Please configure allowedPaymentMethods (e.g. CARD) " +
"to display the Google Pay button."
)
}
}
}
}
}
}

private fun launchGooglePay() {
val activity = requireActivity()
val googlePayRequest = GooglePayRequest(
Settings.getGooglePayCurrency(activity),
"1.00",
GooglePayTotalPriceStatus.TOTAL_PRICE_STATUS_FINAL
).apply {
totalPriceLabel = "Braintree Demo Payment"
allowPrepaidCards = Settings.areGooglePayPrepaidCardsAllowed(activity)
billingAddressFormat = GooglePayBillingAddressFormat.FULL
isBillingAddressRequired = Settings.isGooglePayBillingAddressRequired(activity)
isEmailRequired = Settings.isGooglePayEmailRequired(activity)
isPhoneNumberRequired = Settings.isGooglePayPhoneNumberRequired(activity)
isShippingAddressRequired = Settings.isGooglePayShippingAddressRequired(activity)
shippingAddressParameters = GooglePayShippingAddressParameters(
Settings.getGooglePayAllowedCountriesForShipping(requireContext())
)
checkoutOption = GooglePayCheckoutOption.COMPLETE_IMMEDIATE_PURCHASE
}

googlePayClient.createPaymentAuthRequest(googlePayRequest) { paymentAuthRequest ->
when (paymentAuthRequest) {
is GooglePayPaymentAuthRequest.ReadyToLaunch -> {
googlePayLauncher.launch(paymentAuthRequest)
}

is GooglePayPaymentAuthRequest.Failure -> {
handleError(paymentAuthRequest.error)
}
}
}
}

private fun handleGooglePayActivityResult(paymentMethodNonce: PaymentMethodNonce) {
super.onPaymentMethodNonceCreated(paymentMethodNonce)

val action = GooglePayFragmentDirections.actionGooglePayFragmentToDisplayNonceFragment(
paymentMethodNonce
)
findNavController().navigate(action)
}
}
24 changes: 0 additions & 24 deletions Demo/src/main/res/layout/fragment_google_pay.xml

This file was deleted.

6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ browserSwitch = "3.5.1"
cardinal = "2.2.7-7"
navigationSafeArgsGradlePlugin = "2.5.0"
paypalMessages = "1.1.13"
playServices = "19.4.0"
playServicesWallet = "19.5.0"
junit = "4.13.2"
testParameterInjector = "1.18"
robolectric = "4.14-beta-1"
Expand Down Expand Up @@ -47,6 +47,7 @@ deviceAutomator = "1.0.0"
btCardForm = "5.4.0"
accompanist = "0.37.3"
datastore = "1.2.0"
googlePayButton = "1.1.0"

[libraries]
# Androidx
Expand Down Expand Up @@ -76,7 +77,7 @@ gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
browser-switch = { group = "com.braintreepayments.api", name = "browser-switch", version.ref = "browserSwitch" }
cardinal = { group = "org.jfrog.cardinalcommerce.gradle", name = "cardinalmobilesdk", version.ref = "cardinal" }
paypal-messages = { module = "com.paypal.messages:paypal-messages", version.ref = "paypalMessages" }
play-services-wallet = { group = "com.google.android.gms", name = "play-services-wallet", version.ref = "playServices" }
play-services-wallet = { group = "com.google.android.gms", name = "play-services-wallet", version.ref = "playServicesWallet" }

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.

If this rename isn't strictly necessary could you please leave this as is?

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.

I can revert this but playServices is misleading IMO 🙂 WDYT?

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.

Yeah I agree it's not the most intuitive naming. I generally try to keep unnecessary changes to a minimum since it's critical to keep current integrations functioning as expected but I was too hasty here. Renaming something in this file should be 100% safe since it's only internally accessible. Feel free to leave it

robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
json-assert = { group = "org.skyscreamer", name = "jsonassert", version.ref = "jsonAssert" }
Expand Down Expand Up @@ -117,6 +118,7 @@ bt-card-form = { module = "com.braintreepayments:card-form", version.ref = "btCa
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview"}
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling"}
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
google-pay-button = { module = "com.google.pay.button:compose-pay-button", version.ref = "googlePayButton" }

[plugins]
android-application = { id = "com.android.application", version.ref = "gradle" }
Expand Down
Loading