From 6303e7303f3e9a33bc3d977e8b808534f37a4448 Mon Sep 17 00:00:00 2001 From: chinghlinn Date: Wed, 28 Aug 2024 09:54:39 -0700 Subject: [PATCH] Googlepay kotlin conversion (#1114) * Convert GooglePay module classess to Kotlin * Fix unit tests * Suppress warnings as refactoring is required * Update variable name * Improve null and emtpy string check * Remove doulbe exclamations * Parcelize GooglePayRequest using kotlin-parcelize plugin * Improve return block in GooglePayCapabilities --------- Co-authored-by: Ching-Hsiang Lin --- CHANGELOG.md | 1 + .../BraintreeGooglePayWalletConstants.java | 15 - .../BraintreeGooglePayWalletConstants.kt | 14 + .../api/googlepay/GooglePayActivity.java | 59 -- .../api/googlepay/GooglePayActivity.kt | 64 ++ .../GooglePayActivityResultContract.java | 48 -- .../GooglePayActivityResultContract.kt | 49 ++ .../api/googlepay/GooglePayCapabilities.java | 32 - .../api/googlepay/GooglePayCapabilities.kt | 32 + .../api/googlepay/GooglePayClient.java | 558 ---------------- .../api/googlepay/GooglePayClient.kt | 620 ++++++++++++++++++ .../api/googlepay/GooglePayException.java | 57 -- .../api/googlepay/GooglePayException.kt | 19 + .../googlepay/GooglePayInternalClient.java | 40 -- .../api/googlepay/GooglePayInternalClient.kt | 48 ++ .../api/googlepay/GooglePayLauncher.java | 67 -- .../api/googlepay/GooglePayLauncher.kt | 73 +++ .../GooglePayPaymentAuthRequestParams.java | 28 - .../GooglePayPaymentAuthRequestParams.kt | 12 + .../googlepay/GooglePayPaymentAuthResult.java | 28 - .../googlepay/GooglePayPaymentAuthResult.kt | 12 + .../api/googlepay/GooglePayRequest.java | 538 --------------- .../api/googlepay/GooglePayRequest.kt | 316 +++++++++ .../GooglePayTokenizationParameters.kt | 2 +- .../googlepay/ReadyForGooglePayRequest.java | 25 - .../api/googlepay/ReadyForGooglePayRequest.kt | 17 + .../googlepay/GooglePayClientUnitTest.java | 89 +-- .../googlepay/GooglePayLauncherUnitTest.java | 26 +- .../googlepay/GooglePayRequestUnitTest.java | 2 +- 29 files changed, 1306 insertions(+), 1585 deletions(-) delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.kt delete mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.java create mode 100644 GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4750f7237b..1756cd1d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ * Change `GooglePayGetTokenizationParametersCallback` parameters * Rename `GooglePayLauncherCallback#onResult` to `GooglePayLauncherCallback#onGooglePayLauncherResult` + * Change `GooglePayRequest#isCreditCardsAllowed` to `GooglePayRequest#getAllowCreditCards` * ThreeDSecure * Remove `ThreeDSecureListener` * Add `ThreeDSecureLauncher`, `ThreeDSecurePaymentAuthResult`, diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.java deleted file mode 100644 index d2744f76f3..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import com.google.android.gms.wallet.WalletConstants; - -/** - * Collection of constant values used by the Braintree SDK Google Payment module. Extends upon - * com.google.android.gms.wallet.WalletConstants. - */ -class BraintreeGooglePayWalletConstants { - - /** - * Card network Elo. - */ - static final int CARD_NETWORK_ELO = WalletConstants.CARD_NETWORK_OTHER + 1; -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.kt new file mode 100644 index 0000000000..21083d7a96 --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/BraintreeGooglePayWalletConstants.kt @@ -0,0 +1,14 @@ +package com.braintreepayments.api.googlepay + +import com.google.android.gms.wallet.WalletConstants + +/** + * Collection of constant values used by the Braintree SDK Google Payment module. Extends upon + * com.google.android.gms.wallet.WalletConstants. + */ +internal object BraintreeGooglePayWalletConstants { + /** + * Card network Elo. + */ + const val CARD_NETWORK_ELO: Int = WalletConstants.CARD_NETWORK_OTHER + 1 +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.java deleted file mode 100644 index 45dc930c7e..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.content.Intent; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; - -import com.google.android.gms.wallet.AutoResolveHelper; -import com.google.android.gms.wallet.PaymentDataRequest; -import com.google.android.gms.wallet.PaymentsClient; -import com.google.android.gms.wallet.Wallet; -import com.google.android.gms.wallet.WalletConstants; - -public class GooglePayActivity extends AppCompatActivity { - - protected static final String EXTRA_ENVIRONMENT = "com.braintreepayments.api.EXTRA_ENVIRONMENT"; - protected static final String EXTRA_PAYMENT_DATA_REQUEST = "com.braintreepayments.api.EXTRA_PAYMENT_DATA_REQUEST"; - - private static final String EXTRA_RECREATING = "com.braintreepayments.api.EXTRA_RECREATING"; - - private static final int REQUEST_CODE = 1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_RECREATING)) { - return; - } - - PaymentsClient paymentsClient = Wallet.getPaymentsClient(this, new Wallet.WalletOptions.Builder() - .setEnvironment(getIntent().getIntExtra(EXTRA_ENVIRONMENT, WalletConstants.ENVIRONMENT_TEST)) - .build()); - - PaymentDataRequest request = getIntent().getParcelableExtra(EXTRA_PAYMENT_DATA_REQUEST); - AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(request), this, REQUEST_CODE); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(EXTRA_RECREATING, true); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - setResult(resultCode, data); - finish(); - } - - @Override - public void finish() { - super.finish(); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - } -} \ 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 new file mode 100644 index 0000000000..639ce1b7db --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivity.kt @@ -0,0 +1,64 @@ +package com.braintreepayments.api.googlepay + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +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 + +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.getParcelableExtra(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() + 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.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.java deleted file mode 100644 index 78cf4fe564..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import static android.app.Activity.RESULT_CANCELED; -import static android.app.Activity.RESULT_OK; -import static com.braintreepayments.api.googlepay.GooglePayClient.EXTRA_ENVIRONMENT; -import static com.braintreepayments.api.googlepay.GooglePayClient.EXTRA_PAYMENT_DATA_REQUEST; -import static com.google.android.gms.wallet.AutoResolveHelper.RESULT_ERROR; - -import android.content.Context; -import android.content.Intent; - -import androidx.activity.result.contract.ActivityResultContract; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -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; - -class GooglePayActivityResultContract extends ActivityResultContract { - - @NonNull - @Override - public Intent createIntent(@NonNull Context context, GooglePayPaymentAuthRequestParams input) { - return new Intent(context, GooglePayActivity.class) - .putExtra(EXTRA_ENVIRONMENT, input.getGooglePayEnvironment()) - .putExtra(EXTRA_PAYMENT_DATA_REQUEST, input.getPaymentDataRequest()); - } - - @Override - public GooglePayPaymentAuthResult parseResult(int resultCode, @Nullable Intent intent) { - if (resultCode == RESULT_OK) { - if (intent != null) { - return new GooglePayPaymentAuthResult(PaymentData.getFromIntent(intent), null); - } - } else if (resultCode == RESULT_CANCELED) { - return new GooglePayPaymentAuthResult(null, new UserCanceledException("User canceled Google Pay.")); - } else if (resultCode == RESULT_ERROR) { - if (intent != null) { - return new GooglePayPaymentAuthResult(null, new GooglePayException("An error was encountered during the Google Pay " + - "flow. See the status object in this exception for more details.", - AutoResolveHelper.getStatusFromIntent(intent))); - } - } - return new GooglePayPaymentAuthResult(null, new BraintreeException("An unexpected error occurred.")); - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt new file mode 100644 index 0000000000..ee1f2e8fba --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayActivityResultContract.kt @@ -0,0 +1,49 @@ +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/GooglePayCapabilities.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.java deleted file mode 100644 index 32e45dd3e1..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.braintreepayments.api.core.Configuration; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; -import com.google.android.gms.wallet.Wallet; - -/** - * Class representing Google Pay payment capabilities - */ -public class GooglePayCapabilities { - - /** - * @return {@code true} if Google Pay is enabled and supported in the current environment, - * {@code false} otherwise. Note: this value only pertains to the Braintree configuration, to check if - * the user has Google Pay setup use {@link GooglePayClient#isReadyToPay(Context, GooglePayIsReadyToPayCallback)} - */ - public static boolean isGooglePayEnabled(@NonNull Context context, @NonNull Configuration configuration) { - try { - Class.forName(Wallet.class.getName()); - - return configuration.isGooglePayEnabled() && GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == - ConnectionResult.SUCCESS; - } catch (ClassNotFoundException | NoClassDefFoundError e) { - return false; - } - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.kt new file mode 100644 index 0000000000..66779926bf --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayCapabilities.kt @@ -0,0 +1,32 @@ +package com.braintreepayments.api.googlepay + +import android.content.Context +import com.braintreepayments.api.core.Configuration +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.wallet.Wallet + +/** + * Class representing Google Pay payment capabilities + */ +object GooglePayCapabilities { + /** + * @return `true` if Google Pay is enabled and supported in the current environment, + * `false` otherwise. Note: this value only pertains to the Braintree configuration, to check if + * the user has Google Pay setup use [GooglePayClient.isReadyToPay] + */ + @SuppressWarnings("SwallowedException") + fun isGooglePayEnabled(context: Context, configuration: Configuration): Boolean { + return try { + Class.forName(Wallet::class.java.name) + + configuration.isGooglePayEnabled && GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(context) == + ConnectionResult.SUCCESS + } catch (e: ClassNotFoundException) { + false + } catch (e: NoClassDefFoundError) { + false + } + } +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.java deleted file mode 100644 index 8e7b0c313f..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.java +++ /dev/null @@ -1,558 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.braintreepayments.api.paypal.PayPalAccountNonce; -import com.braintreepayments.api.core.Authorization; -import com.braintreepayments.api.core.BraintreeClient; -import com.braintreepayments.api.core.BraintreeException; -import com.braintreepayments.api.core.Configuration; -import com.braintreepayments.api.core.ErrorWithResponse; -import com.braintreepayments.api.core.MetadataBuilder; -import com.braintreepayments.api.core.PaymentMethodNonce; -import com.braintreepayments.api.core.TokenizationKey; -import com.braintreepayments.api.core.UserCanceledException; -import com.google.android.gms.wallet.CardRequirements; -import com.google.android.gms.wallet.IsReadyToPayRequest; -import com.google.android.gms.wallet.PaymentData; -import com.google.android.gms.wallet.PaymentDataRequest; -import com.google.android.gms.wallet.PaymentMethodTokenizationParameters; -import com.google.android.gms.wallet.PaymentsClient; -import com.google.android.gms.wallet.WalletConstants; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Used to create and tokenize Google Pay payment methods. For more information see the documentation - */ -public class GooglePayClient { - - protected static final String EXTRA_ENVIRONMENT = "com.braintreepayments.api.EXTRA_ENVIRONMENT"; - protected static final String EXTRA_PAYMENT_DATA_REQUEST = - "com.braintreepayments.api.EXTRA_PAYMENT_DATA_REQUEST"; - - private static final String VISA_NETWORK = "visa"; - private static final String MASTERCARD_NETWORK = "mastercard"; - private static final String AMEX_NETWORK = "amex"; - private static final String DISCOVER_NETWORK = "discover"; - private static final String ELO_NETWORK = "elo"; - - private static final String CARD_PAYMENT_TYPE = "CARD"; - private static final String PAYPAL_PAYMENT_TYPE = "PAYPAL"; - - private final BraintreeClient braintreeClient; - private final GooglePayInternalClient internalGooglePayClient; - - /** - * Initializes a new {@link GooglePayClient} instance - * - * @param context an Android Context - * @param authorization a Tokenization Key or Client Token used to authenticate - */ - public GooglePayClient(@NonNull Context context, @NonNull String authorization) { - this(new BraintreeClient(context, authorization)); - } - - @VisibleForTesting - GooglePayClient(@NonNull BraintreeClient braintreeClient) { - this(braintreeClient, new GooglePayInternalClient()); - } - - @VisibleForTesting - GooglePayClient(BraintreeClient braintreeClient, - GooglePayInternalClient internalGooglePayClient) { - this.braintreeClient = braintreeClient; - this.internalGooglePayClient = internalGooglePayClient; - } - - /** - * Before starting the Google Pay flow, use this method to check whether the Google Pay API is - * supported and set up on the device. When the callback is called with {@code true}, show the - * Google Pay button. When it is called with {@code false}, display other checkout options. - * - * @param context Android Context - * @param callback {@link GooglePayIsReadyToPayCallback} - */ - public void isReadyToPay(@NonNull final Context context, - @NonNull final GooglePayIsReadyToPayCallback callback) { - isReadyToPay(context, null, callback); - } - - /** - * Before starting the Google Pay flow, use this method to check whether the Google Pay API is - * supported and set up on the device. When the callback is called with {@code true}, show the - * Google Pay button. When it is called with {@code false}, display other checkout options. - * - * @param context Android Context - * @param request {@link ReadyForGooglePayRequest} - * @param callback {@link GooglePayIsReadyToPayCallback} - */ - public void isReadyToPay(@NonNull final Context context, - @Nullable final ReadyForGooglePayRequest request, - @NonNull final GooglePayIsReadyToPayCallback callback) { - try { - Class.forName(PaymentsClient.class.getName()); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(null)); - return; - } - - braintreeClient.getConfiguration((configuration, e) -> { - if (configuration == null) { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(e)); - return; - } - - if (!configuration.isGooglePayEnabled()) { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(null)); - return; - } - - //noinspection ConstantConditions - if (context == null) { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(new IllegalArgumentException("Activity cannot be null."))); - return; - } - - JSONObject json = new JSONObject(); - JSONArray allowedCardNetworks = buildCardNetworks(configuration); - - try { - json.put("apiVersion", 2).put("apiVersionMinor", 0).put("allowedPaymentMethods", - new JSONArray().put(new JSONObject().put("type", "CARD") - .put("parameters", new JSONObject().put("allowedAuthMethods", - new JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS")) - .put("allowedCardNetworks", allowedCardNetworks)))); - - if (request != null) { - json.put("existingPaymentMethodRequired", - request.isExistingPaymentMethodRequired()); - } - - } catch (JSONException ignored) { - } - IsReadyToPayRequest request1 = IsReadyToPayRequest.fromJson(json.toString()); - internalGooglePayClient.isReadyToPay(context, configuration, request1, callback); - }); - } - - /** - * Get Braintree specific tokenization parameters for a Google Pay. Useful for when full control - * over the {@link PaymentDataRequest} is required. - *

- * {@link PaymentMethodTokenizationParameters} should be supplied to the - * {@link PaymentDataRequest} via - * {@link - * PaymentDataRequest.Builder#setPaymentMethodTokenizationParameters(PaymentMethodTokenizationParameters)} - * and {@link Collection } allowedCardNetworks should be supplied to the - * {@link CardRequirements} via - * {@link CardRequirements.Builder#addAllowedCardNetworks(Collection)}}. - * - * @param callback {@link GooglePayGetTokenizationParametersCallback} - */ - public void getTokenizationParameters( - @NonNull final GooglePayGetTokenizationParametersCallback callback) { - braintreeClient.getConfiguration((configuration, e) -> { - if (configuration == null && e != null) { - callback.onTokenizationParametersResult(new GooglePayTokenizationParameters.Failure(e)); - return; - } - callback.onTokenizationParametersResult(new GooglePayTokenizationParameters.Success( - getTokenizationParameters(configuration, braintreeClient.getAuthorization()), getAllowedCardNetworks(configuration))); - }); - - } - - /** - * Start the Google Pay payment flow. This will return {@link GooglePayPaymentAuthRequestParams} that are - * used to present Google Pay payment sheet in - * {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequestParams)} - * - * @param request The {@link GooglePayRequest} containing options for the transaction. - * @param callback {@link GooglePayPaymentAuthRequestCallback} - */ - public void createPaymentAuthRequest(@NonNull final GooglePayRequest request, - @NonNull final GooglePayPaymentAuthRequestCallback callback) { - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_STARTED); - - if (!validateManifest()) { - callbackPaymentRequestFailure(new GooglePayPaymentAuthRequest.Failure(new BraintreeException( - "GooglePayActivity was not found in the Android " + - "manifest, or did not have a theme of R.style.bt_transparent_activity")), callback); - return; - } - - //noinspection ConstantConditions - if (request == null) { - callbackPaymentRequestFailure(new GooglePayPaymentAuthRequest.Failure(new BraintreeException( - "Cannot pass null GooglePayRequest to requestPayment")), callback); - return; - } - - if (request.getTransactionInfo() == null) { - callbackPaymentRequestFailure(new GooglePayPaymentAuthRequest.Failure(new BraintreeException( - "Cannot pass null TransactionInfo to requestPayment")), callback); - return; - } - - braintreeClient.getConfiguration((configuration, configError) -> { - if (configuration == null && configError != null) { - callbackPaymentRequestFailure(new GooglePayPaymentAuthRequest.Failure(configError), callback); - return; - } - - if (!configuration.isGooglePayEnabled()) { - callbackPaymentRequestFailure(new GooglePayPaymentAuthRequest.Failure(new BraintreeException( - "Google Pay is not enabled for your Braintree account, or Google Play Services are not configured correctly.")), callback); - return; - } - - setGooglePayRequestDefaults(configuration, braintreeClient.getAuthorization(), request); - - PaymentDataRequest paymentDataRequest = - PaymentDataRequest.fromJson(request.toJson()); - - GooglePayPaymentAuthRequestParams params = - new GooglePayPaymentAuthRequestParams(getGooglePayEnvironment(configuration), - paymentDataRequest); - callbackPaymentRequestSuccess(new GooglePayPaymentAuthRequest.ReadyToLaunch(params), callback); - - }); - - } - - /** - * Call this method when you've received a successful {@link PaymentData} response from a - * direct Google Play Services integration to get a {@link GooglePayCardNonce} or - * {@link PayPalAccountNonce}. - * - * @param paymentData {@link PaymentData} retrieved from directly integrating with Google Play - * Services through {@link PaymentsClient#loadPaymentData(PaymentDataRequest)} - * @param callback {@link GooglePayTokenizeCallback} - */ - void tokenize(PaymentData paymentData, GooglePayTokenizeCallback callback) { - try { - JSONObject result = new JSONObject(paymentData.toJson()); - callbackTokenizeSuccess(new GooglePayResult.Success(GooglePayCardNonce.fromJSON(result)), callback); - } catch (JSONException | NullPointerException e) { - try { - String token = - new JSONObject(paymentData.toJson()).getJSONObject("paymentMethodData") - .getJSONObject("tokenizationData").getString("token"); - callbackTokenizeFailure(new GooglePayResult.Failure(ErrorWithResponse.fromJson(token)), callback); - } catch (JSONException | NullPointerException e1) { - callbackTokenizeFailure(new GooglePayResult.Failure(e1), callback); - } - } - } - - - /** - * After a user successfully authorizes Google Pay payment via - * {@link GooglePayClient#createPaymentAuthRequest(GooglePayRequest, GooglePayPaymentAuthRequestCallback)}, this - * method should be invoked to tokenize the payment method to retrieve a - * {@link PaymentMethodNonce} - * - * @param paymentAuthResult the result of {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequestParams)} - * @param callback {@link GooglePayTokenizeCallback} - */ - public void tokenize(GooglePayPaymentAuthResult paymentAuthResult, - GooglePayTokenizeCallback callback) { - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_STARTED); - if (paymentAuthResult.getPaymentData() != null) { - tokenize(paymentAuthResult.getPaymentData(), callback); - } else if (paymentAuthResult.getError() != null) { - if (paymentAuthResult.getError() instanceof UserCanceledException) { - callbackTokenizeCancel(callback); - return; - } - callbackTokenizeFailure(new GooglePayResult.Failure(paymentAuthResult.getError()), callback); - } - } - - int getGooglePayEnvironment(Configuration configuration) { - if ("production".equals(configuration.getGooglePayEnvironment())) { - return WalletConstants.ENVIRONMENT_PRODUCTION; - } else { - return WalletConstants.ENVIRONMENT_TEST; - } - } - - PaymentMethodTokenizationParameters getTokenizationParameters(Configuration configuration, - Authorization authorization) { - String version; - - JSONObject metadata = - new MetadataBuilder().integration(braintreeClient.getIntegrationType()) - .sessionId(braintreeClient.getSessionId()).version().build(); - - try { - version = metadata.getString("version"); - } catch (JSONException e) { - version = com.braintreepayments.api.core.BuildConfig.VERSION_NAME; - } - - PaymentMethodTokenizationParameters.Builder parameters = - PaymentMethodTokenizationParameters.newBuilder().setPaymentMethodTokenizationType( - WalletConstants.PAYMENT_METHOD_TOKENIZATION_TYPE_PAYMENT_GATEWAY) - .addParameter("gateway", "braintree") - .addParameter("braintree:merchantId", configuration.getMerchantId()) - .addParameter("braintree:authorizationFingerprint", - configuration.getGooglePayAuthorizationFingerprint()) - .addParameter("braintree:apiVersion", "v1") - .addParameter("braintree:sdkVersion", version) - .addParameter("braintree:metadata", metadata.toString()); - - if (authorization instanceof TokenizationKey) { - parameters.addParameter("braintree:clientKey", authorization.getBearer()); - } - - return parameters.build(); - } - - ArrayList getAllowedCardNetworks(Configuration configuration) { - ArrayList allowedNetworks = new ArrayList<>(); - for (String network : configuration.getGooglePaySupportedNetworks()) { - switch (network) { - case VISA_NETWORK: - allowedNetworks.add(WalletConstants.CARD_NETWORK_VISA); - break; - case MASTERCARD_NETWORK: - allowedNetworks.add(WalletConstants.CARD_NETWORK_MASTERCARD); - break; - case AMEX_NETWORK: - allowedNetworks.add(WalletConstants.CARD_NETWORK_AMEX); - break; - case DISCOVER_NETWORK: - allowedNetworks.add(WalletConstants.CARD_NETWORK_DISCOVER); - break; - case ELO_NETWORK: - allowedNetworks.add(BraintreeGooglePayWalletConstants.CARD_NETWORK_ELO); - break; - default: - break; - } - } - - return allowedNetworks; - } - - private JSONArray buildCardNetworks(Configuration configuration) { - JSONArray cardNetworkStrings = new JSONArray(); - - for (int network : getAllowedCardNetworks(configuration)) { - switch (network) { - case WalletConstants.CARD_NETWORK_AMEX: - cardNetworkStrings.put("AMEX"); - break; - case WalletConstants.CARD_NETWORK_DISCOVER: - cardNetworkStrings.put("DISCOVER"); - break; - case WalletConstants.CARD_NETWORK_JCB: - cardNetworkStrings.put("JCB"); - break; - case WalletConstants.CARD_NETWORK_MASTERCARD: - cardNetworkStrings.put("MASTERCARD"); - break; - case WalletConstants.CARD_NETWORK_VISA: - cardNetworkStrings.put("VISA"); - break; - case BraintreeGooglePayWalletConstants.CARD_NETWORK_ELO: - cardNetworkStrings.put("ELO"); - cardNetworkStrings.put("ELO_DEBIT"); - break; - } - } - return cardNetworkStrings; - } - - private JSONObject buildCardPaymentMethodParameters(Configuration configuration, - GooglePayRequest request) { - JSONObject defaultParameters = new JSONObject(); - - try { - if (request.getAllowedCardNetworksForType(CARD_PAYMENT_TYPE) == null) { - JSONArray cardNetworkStrings = buildCardNetworks(configuration); - - if (request.getAllowedAuthMethodsForType(CARD_PAYMENT_TYPE) == null) { - request.setAllowedAuthMethods(CARD_PAYMENT_TYPE, - new JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS")); - } else { - request.setAllowedAuthMethods(CARD_PAYMENT_TYPE, - request.getAllowedAuthMethodsForType(CARD_PAYMENT_TYPE)); - } - - request.setAllowedCardNetworks(CARD_PAYMENT_TYPE, cardNetworkStrings); - } - - defaultParameters.put("billingAddressRequired", request.isBillingAddressRequired()) - .put("allowPrepaidCards", request.getAllowPrepaidCards()) - .put("allowedAuthMethods", - request.getAllowedAuthMethodsForType(CARD_PAYMENT_TYPE)) - .put("allowedCardNetworks", - request.getAllowedCardNetworksForType(CARD_PAYMENT_TYPE)); - - if (request.isBillingAddressRequired()) { - defaultParameters.put("billingAddressParameters", - new JSONObject().put("format", request.billingAddressFormatToString()) - .put("phoneNumberRequired", request.isPhoneNumberRequired())); - } - } catch (JSONException ignored) { - } - return defaultParameters; - } - - private JSONObject buildPayPalPaymentMethodParameters(Configuration configuration) { - JSONObject defaultParameters = new JSONObject(); - - try { - JSONObject purchaseContext = new JSONObject().put("purchase_units", new JSONArray().put( - new JSONObject().put("payee", new JSONObject().put("client_id", - configuration.getGooglePayPayPalClientId())) - .put("recurring_payment", "true"))); - - defaultParameters.put("purchase_context", purchaseContext); - } catch (JSONException ignored) { - } - - return defaultParameters; - - } - - private JSONObject buildCardTokenizationSpecification(Configuration configuration, - Authorization authorization) { - JSONObject cardJson = new JSONObject(); - JSONObject parameters = new JSONObject(); - String googlePayVersion = com.braintreepayments.api.googlepay.BuildConfig.VERSION_NAME; - - try { - parameters.put("gateway", "braintree").put("braintree:apiVersion", "v1") - .put("braintree:sdkVersion", googlePayVersion) - .put("braintree:merchantId", configuration.getMerchantId()) - .put("braintree:metadata", (new JSONObject().put("source", "client") - .put("integration", braintreeClient.getIntegrationType()) - .put("sessionId", braintreeClient.getSessionId()) - .put("version", googlePayVersion) - .put("platform", "android")).toString()); - - if (authorization instanceof TokenizationKey) { - parameters.put("braintree:clientKey", authorization.toString()); - } else { - String googlePayAuthFingerprint = - configuration.getGooglePayAuthorizationFingerprint(); - parameters.put("braintree:authorizationFingerprint", googlePayAuthFingerprint); - } - } catch (JSONException ignored) { - } - - try { - cardJson.put("type", "PAYMENT_GATEWAY").put("parameters", parameters); - } catch (JSONException ignored) { - } - - return cardJson; - } - - private JSONObject buildPayPalTokenizationSpecification(Configuration configuration) { - JSONObject json = new JSONObject(); - String googlePayVersion = com.braintreepayments.api.googlepay.BuildConfig.VERSION_NAME; - - try { - json.put("type", "PAYMENT_GATEWAY").put("parameters", - new JSONObject().put("gateway", "braintree").put("braintree:apiVersion", "v1") - .put("braintree:sdkVersion", googlePayVersion) - .put("braintree:merchantId", configuration.getMerchantId()) - .put("braintree:paypalClientId", - configuration.getGooglePayPayPalClientId()) - .put("braintree:metadata", (new JSONObject().put("source", "client") - .put("integration", braintreeClient.getIntegrationType()) - .put("sessionId", braintreeClient.getSessionId()) - .put("version", googlePayVersion) - .put("platform", "android")).toString())); - } catch (JSONException ignored) { - } - - return json; - } - - private void setGooglePayRequestDefaults(Configuration configuration, - Authorization authorization, - GooglePayRequest request) { - - if (request.getAllowedPaymentMethod(CARD_PAYMENT_TYPE) == null) { - request.setAllowedPaymentMethod(CARD_PAYMENT_TYPE, - buildCardPaymentMethodParameters(configuration, request)); - } - - if (request.getTokenizationSpecificationForType(CARD_PAYMENT_TYPE) == null) { - request.setTokenizationSpecificationForType("CARD", - buildCardTokenizationSpecification(configuration, authorization)); - } - - boolean googlePayCanProcessPayPal = request.isPayPalEnabled() && - !TextUtils.isEmpty(configuration.getGooglePayPayPalClientId()); - - if (googlePayCanProcessPayPal) { - if (request.getAllowedPaymentMethod("PAYPAL") == null) { - request.setAllowedPaymentMethod(PAYPAL_PAYMENT_TYPE, - buildPayPalPaymentMethodParameters(configuration)); - } - - - if (request.getTokenizationSpecificationForType(PAYPAL_PAYMENT_TYPE) == null) { - request.setTokenizationSpecificationForType("PAYPAL", - buildPayPalTokenizationSpecification(configuration)); - } - } - - request.setEnvironment(configuration.getGooglePayEnvironment()); - } - - private boolean validateManifest() { - ActivityInfo activityInfo = - braintreeClient.getManifestActivityInfo(GooglePayActivity.class); - return activityInfo != null && - activityInfo.getThemeResource() == R.style.bt_transparent_activity; - } - - private void callbackPaymentRequestSuccess(@NonNull GooglePayPaymentAuthRequest.ReadyToLaunch request, - GooglePayPaymentAuthRequestCallback callback) { - callback.onGooglePayPaymentAuthRequest(request); - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_SUCCEEDED); - } - - private void callbackPaymentRequestFailure(@NonNull GooglePayPaymentAuthRequest.Failure request, - GooglePayPaymentAuthRequestCallback callback) { - callback.onGooglePayPaymentAuthRequest(request); - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED); - } - - private void callbackTokenizeSuccess(@NonNull GooglePayResult.Success result, - GooglePayTokenizeCallback callback) { - callback.onGooglePayResult(result); - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_SUCCEEDED); - } - - private void callbackTokenizeCancel(GooglePayTokenizeCallback callback) { - callback.onGooglePayResult(GooglePayResult.Cancel.INSTANCE); - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_SHEET_CANCELED); - } - - private void callbackTokenizeFailure(@NonNull GooglePayResult.Failure result, - GooglePayTokenizeCallback callback) { - callback.onGooglePayResult(result); - braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_FAILED); - } -} \ No newline at end of file diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt new file mode 100644 index 0000000000..b2f3ffd7fd --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayClient.kt @@ -0,0 +1,620 @@ +package com.braintreepayments.api.googlepay + +import android.content.Context +import android.text.TextUtils +import androidx.annotation.VisibleForTesting +import com.braintreepayments.api.core.Authorization +import com.braintreepayments.api.core.BraintreeClient +import com.braintreepayments.api.core.BraintreeException +import com.braintreepayments.api.core.Configuration +import com.braintreepayments.api.core.ErrorWithResponse.Companion.fromJson +import com.braintreepayments.api.core.MetadataBuilder +import com.braintreepayments.api.core.TokenizationKey +import com.braintreepayments.api.core.UserCanceledException +import com.braintreepayments.api.googlepay.GooglePayCardNonce.Companion.fromJSON +import com.braintreepayments.api.googlepay.GooglePayReadinessResult.NotReadyToPay +import com.google.android.gms.wallet.IsReadyToPayRequest +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentDataRequest +import com.google.android.gms.wallet.PaymentMethodTokenizationParameters +import com.google.android.gms.wallet.PaymentsClient +import com.google.android.gms.wallet.WalletConstants +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +/** + * Used to create and tokenize Google Pay payment methods. For more information see the [documentation](https://developer.paypal.com/braintree/docs/guides/google-pay/overview) + */ +@SuppressWarnings("TooManyFunctions") +class GooglePayClient @VisibleForTesting internal constructor( + private val braintreeClient: BraintreeClient, + private val internalGooglePayClient: GooglePayInternalClient = GooglePayInternalClient() +) { + /** + * Initializes a new [GooglePayClient] instance + * + * @param context an Android Context + * @param authorization a Tokenization Key or Client Token used to authenticate + */ + constructor(context: Context, authorization: String) : this( + BraintreeClient( + context, + authorization + ) + ) + + /** + * Before starting the Google Pay flow, use this method to check whether the Google Pay API is + * supported and set up on the device. When the callback is called with `true`, show the + * Google Pay button. When it is called with `false`, display other checkout options. + * + * @param context Android Context + * @param callback [GooglePayIsReadyToPayCallback] + */ + fun isReadyToPay( + context: Context, + callback: GooglePayIsReadyToPayCallback + ) { + isReadyToPay(context, null, callback) + } + + /** + * Before starting the Google Pay flow, use this method to check whether the Google Pay API is + * supported and set up on the device. When the callback is called with `true`, show the + * Google Pay button. When it is called with `false`, display other checkout options. + * + * @param context Android Context + * @param request [ReadyForGooglePayRequest] + * @param callback [GooglePayIsReadyToPayCallback] + */ + @SuppressWarnings("SwallowedException") + fun isReadyToPay( + context: Context, + request: ReadyForGooglePayRequest?, + callback: GooglePayIsReadyToPayCallback + ) { + try { + Class.forName(PaymentsClient::class.java.name) + } catch (e: ClassNotFoundException) { + callback.onGooglePayReadinessResult(NotReadyToPay(null)) + return + } catch (e: NoClassDefFoundError) { + callback.onGooglePayReadinessResult(NotReadyToPay(null)) + return + } + + braintreeClient.getConfiguration { configuration: Configuration?, e: Exception? -> + if (configuration == null) { + callback.onGooglePayReadinessResult(NotReadyToPay(e)) + return@getConfiguration + } + if (!configuration.isGooglePayEnabled) { + callback.onGooglePayReadinessResult(NotReadyToPay(null)) + return@getConfiguration + } + + val json = JSONObject() + val allowedCardNetworks = buildCardNetworks(configuration) + + try { + json.put("apiVersion", 2).put("apiVersionMinor", 0).put( + "allowedPaymentMethods", + JSONArray().put( + JSONObject().put("type", "CARD") + .put( + "parameters", JSONObject().put( + "allowedAuthMethods", + JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS") + ) + .put("allowedCardNetworks", allowedCardNetworks) + ) + ) + ) + + if (request != null) { + json.put( + "existingPaymentMethodRequired", + request.isExistingPaymentMethodRequired + ) + } + } catch (ignored: JSONException) { + } + val readyToPayRequest = IsReadyToPayRequest.fromJson(json.toString()) + internalGooglePayClient.isReadyToPay(context, configuration, readyToPayRequest, callback) + } + } + + /** + * Get Braintree specific tokenization parameters for a Google Pay. Useful for when full control + * over the [PaymentDataRequest] is required. + * + * + * [PaymentMethodTokenizationParameters] should be supplied to the + * [PaymentDataRequest] via + * [ ][PaymentDataRequest.Builder.setPaymentMethodTokenizationParameters] + * and [<Integer>][Collection] allowedCardNetworks should be supplied to the + * [CardRequirements] via + * [CardRequirements.Builder.addAllowedCardNetworks]}. + * + * @param callback [GooglePayGetTokenizationParametersCallback] + */ + fun getTokenizationParameters( + callback: GooglePayGetTokenizationParametersCallback + ) { + braintreeClient.getConfiguration { configuration: Configuration?, e: Exception? -> + + if (configuration != null) { + callback.onTokenizationParametersResult( + GooglePayTokenizationParameters.Success( + getTokenizationParameters(configuration, braintreeClient.authorization), + getAllowedCardNetworks(configuration) + ) + ) + } else { + if (e != null) { + callback.onTokenizationParametersResult(GooglePayTokenizationParameters.Failure(e)) + } else { + callback.onTokenizationParametersResult(null) + } + } + } + } + + /** + * Start the Google Pay payment flow. This will return [GooglePayPaymentAuthRequestParams] that are + * used to present Google Pay payment sheet in + * [GooglePayLauncher.launch] + * + * @param request The [GooglePayRequest] containing options for the transaction. + * @param callback [GooglePayPaymentAuthRequestCallback] + */ + @SuppressWarnings("LongMethod") + fun createPaymentAuthRequest( + request: GooglePayRequest, + callback: GooglePayPaymentAuthRequestCallback + ) { + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_STARTED) + + if (!validateManifest()) { + callbackPaymentRequestFailure( + GooglePayPaymentAuthRequest.Failure( + BraintreeException( + "GooglePayActivity was not found in the Android " + + "manifest, or did not have a theme of R.style.bt_transparent_activity" + ) + ), callback + ) + return + } + + if (request.transactionInfo == null) { + callbackPaymentRequestFailure( + GooglePayPaymentAuthRequest.Failure( + BraintreeException( + "Cannot pass null TransactionInfo to requestPayment" + ) + ), callback + ) + return + } + + braintreeClient.getConfiguration { configuration: Configuration?, configError: Exception? -> + + if (configuration?.isGooglePayEnabled == true) { + setGooglePayRequestDefaults(configuration, braintreeClient.authorization, request) + + val paymentDataRequest = + PaymentDataRequest.fromJson(request.toJson()) + + val params = + GooglePayPaymentAuthRequestParams( + getGooglePayEnvironment(configuration), + paymentDataRequest + ) + callbackPaymentRequestSuccess( + GooglePayPaymentAuthRequest.ReadyToLaunch(params), + callback + ) + } else { + if (configError == null) { + callbackPaymentRequestFailure( + GooglePayPaymentAuthRequest.Failure( + BraintreeException( + "Google Pay is not enabled for your Braintree account, " + + "or Google Play Services are not configured correctly." + ) + ), callback + ) + } else { + callbackPaymentRequestFailure( + GooglePayPaymentAuthRequest.Failure(configError), + callback + ) + } + } + } + } + + /** + * Call this method when you've received a successful [PaymentData] response from a + * direct Google Play Services integration to get a [GooglePayCardNonce] or + * [PayPalAccountNonce]. + * + * @param paymentData [PaymentData] retrieved from directly integrating with Google Play + * Services through [PaymentsClient.loadPaymentData] + * @param callback [GooglePayTokenizeCallback] + */ + @SuppressWarnings("TooGenericExceptionCaught") + fun tokenize(paymentData: PaymentData, callback: GooglePayTokenizeCallback) { + try { + val result = JSONObject(paymentData.toJson()) + callbackTokenizeSuccess(GooglePayResult.Success(fromJSON(result)), callback) + } catch (e: JSONException) { + try { + val token = + JSONObject(paymentData.toJson()).getJSONObject("paymentMethodData") + .getJSONObject("tokenizationData").getString("token") + callbackTokenizeFailure(GooglePayResult.Failure(fromJson(token)), callback) + } catch (e1: JSONException) { + callbackTokenizeFailure(GooglePayResult.Failure(e1), callback) + } catch (e1: NullPointerException) { + callbackTokenizeFailure(GooglePayResult.Failure(e1), callback) + } + } catch (e: NullPointerException) { + try { + val token = + JSONObject(paymentData.toJson()).getJSONObject("paymentMethodData") + .getJSONObject("tokenizationData").getString("token") + callbackTokenizeFailure(GooglePayResult.Failure(fromJson(token)), callback) + } catch (e1: JSONException) { + callbackTokenizeFailure(GooglePayResult.Failure(e1), callback) + } catch (e1: NullPointerException) { + callbackTokenizeFailure(GooglePayResult.Failure(e1), callback) + } + } + } + + /** + * After a user successfully authorizes Google Pay payment via + * [GooglePayClient.createPaymentAuthRequest], this + * method should be invoked to tokenize the payment method to retrieve a + * [PaymentMethodNonce] + * + * @param paymentAuthResult the result of [GooglePayLauncher.launch] + * @param callback [GooglePayTokenizeCallback] + */ + fun tokenize( + paymentAuthResult: GooglePayPaymentAuthResult, + callback: GooglePayTokenizeCallback + ) { + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_STARTED) + val paymentData = paymentAuthResult.paymentData + if (paymentData != null) { + tokenize(paymentData, callback) + } else if (paymentAuthResult.error != null) { + if (paymentAuthResult.error is UserCanceledException) { + callbackTokenizeCancel(callback) + return + } + callbackTokenizeFailure(GooglePayResult.Failure(paymentAuthResult.error), callback) + } + } + + private fun getGooglePayEnvironment(configuration: Configuration): Int { + return if ("production" == configuration.googlePayEnvironment) { + WalletConstants.ENVIRONMENT_PRODUCTION + } else { + WalletConstants.ENVIRONMENT_TEST + } + } + + fun getTokenizationParameters( + configuration: Configuration, + authorization: Authorization + ): PaymentMethodTokenizationParameters { + + val metadata = + MetadataBuilder().integration(braintreeClient.integrationType) + .sessionId(braintreeClient.sessionId).version().build() + + val version = try { + metadata.getString("version") + } catch (e: JSONException) { + com.braintreepayments.api.core.BuildConfig.VERSION_NAME + } + + val parameters = + PaymentMethodTokenizationParameters.newBuilder().setPaymentMethodTokenizationType( + WalletConstants.PAYMENT_METHOD_TOKENIZATION_TYPE_PAYMENT_GATEWAY + ) + .addParameter("gateway", "braintree") + .addParameter("braintree:merchantId", configuration.merchantId) + .addParameter("braintree:apiVersion", "v1") + .addParameter("braintree:sdkVersion", version) + .addParameter("braintree:metadata", metadata.toString()) + + val fingerprint = configuration.googlePayAuthorizationFingerprint + if (fingerprint?.isNotEmpty() == true) { + parameters.addParameter("braintree:authorizationFingerprint", fingerprint) + } + + if (authorization is TokenizationKey) { + parameters.addParameter("braintree:clientKey", authorization.bearer) + } + + return parameters.build() + } + + fun getAllowedCardNetworks(configuration: Configuration): ArrayList { + val allowedNetworks = ArrayList() + for (network in configuration.googlePaySupportedNetworks) { + when (network) { + VISA_NETWORK -> allowedNetworks.add(WalletConstants.CARD_NETWORK_VISA) + MASTERCARD_NETWORK -> allowedNetworks.add(WalletConstants.CARD_NETWORK_MASTERCARD) + AMEX_NETWORK -> allowedNetworks.add(WalletConstants.CARD_NETWORK_AMEX) + DISCOVER_NETWORK -> allowedNetworks.add(WalletConstants.CARD_NETWORK_DISCOVER) + ELO_NETWORK -> allowedNetworks.add(BraintreeGooglePayWalletConstants.CARD_NETWORK_ELO) + else -> {} + } + } + + return allowedNetworks + } + + private fun buildCardNetworks(configuration: Configuration): JSONArray { + val cardNetworkStrings = JSONArray() + + for (network in getAllowedCardNetworks(configuration)) { + when (network) { + WalletConstants.CARD_NETWORK_AMEX -> cardNetworkStrings.put("AMEX") + WalletConstants.CARD_NETWORK_DISCOVER -> cardNetworkStrings.put("DISCOVER") + WalletConstants.CARD_NETWORK_JCB -> cardNetworkStrings.put("JCB") + WalletConstants.CARD_NETWORK_MASTERCARD -> cardNetworkStrings.put("MASTERCARD") + WalletConstants.CARD_NETWORK_VISA -> cardNetworkStrings.put("VISA") + BraintreeGooglePayWalletConstants.CARD_NETWORK_ELO -> { + cardNetworkStrings.put("ELO") + cardNetworkStrings.put("ELO_DEBIT") + } + } + } + return cardNetworkStrings + } + + private fun buildCardPaymentMethodParameters( + configuration: Configuration, + request: GooglePayRequest + ): JSONObject { + val defaultParameters = JSONObject() + + try { + if (request.getAllowedCardNetworksForType(CARD_PAYMENT_TYPE) == null) { + val cardNetworkStrings = buildCardNetworks(configuration) + + request.getAllowedAuthMethodsForType(CARD_PAYMENT_TYPE)?.let { jsonArray -> + request.setAllowedAuthMethods( + CARD_PAYMENT_TYPE, + jsonArray + ) + } ?: run { + request.setAllowedAuthMethods( + CARD_PAYMENT_TYPE, + JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS") + ) + } + + request.setAllowedCardNetworks(CARD_PAYMENT_TYPE, cardNetworkStrings) + } + + defaultParameters.put("billingAddressRequired", request.isBillingAddressRequired) + .put("allowPrepaidCards", request.allowPrepaidCards) + .put( + "allowedAuthMethods", + request.getAllowedAuthMethodsForType(CARD_PAYMENT_TYPE) + ) + .put( + "allowedCardNetworks", + request.getAllowedCardNetworksForType(CARD_PAYMENT_TYPE) + ) + + if (request.isBillingAddressRequired) { + defaultParameters.put( + "billingAddressParameters", + JSONObject().put("format", request.billingAddressFormatToString()) + .put("phoneNumberRequired", request.isPhoneNumberRequired) + ) + } + } catch (ignored: JSONException) { + } + return defaultParameters + } + + private fun buildPayPalPaymentMethodParameters(configuration: Configuration): JSONObject { + val defaultParameters = JSONObject() + + try { + val purchaseContext = JSONObject().put( + "purchase_units", JSONArray().put( + JSONObject().put( + "payee", JSONObject().put( + "client_id", + configuration.googlePayPayPalClientId + ) + ) + .put("recurring_payment", "true") + ) + ) + + defaultParameters.put("purchase_context", purchaseContext) + } catch (ignored: JSONException) { + } + + return defaultParameters + } + + private fun buildCardTokenizationSpecification( + configuration: Configuration, + authorization: Authorization + ): JSONObject { + val cardJson = JSONObject() + val parameters = JSONObject() + val googlePayVersion = BuildConfig.VERSION_NAME + + try { + parameters.put("gateway", "braintree").put("braintree:apiVersion", "v1") + .put("braintree:sdkVersion", googlePayVersion) + .put("braintree:merchantId", configuration.merchantId) + .put( + "braintree:metadata", JSONObject().put("source", "client") + .put("integration", braintreeClient.integrationType) + .put("sessionId", braintreeClient.sessionId) + .put("version", googlePayVersion) + .put("platform", "android").toString() + ) + + if (authorization is TokenizationKey) { + parameters.put("braintree:clientKey", authorization.toString()) + } else { + val googlePayAuthFingerprint = + configuration.googlePayAuthorizationFingerprint + parameters.put("braintree:authorizationFingerprint", googlePayAuthFingerprint) + } + } catch (ignored: JSONException) { + } + + try { + cardJson.put("type", "PAYMENT_GATEWAY").put("parameters", parameters) + } catch (ignored: JSONException) { + } + + return cardJson + } + + private fun buildPayPalTokenizationSpecification(configuration: Configuration): JSONObject { + val json = JSONObject() + val googlePayVersion = BuildConfig.VERSION_NAME + + try { + json.put("type", "PAYMENT_GATEWAY").put( + "parameters", + JSONObject().put("gateway", "braintree").put("braintree:apiVersion", "v1") + .put("braintree:sdkVersion", googlePayVersion) + .put("braintree:merchantId", configuration.merchantId) + .put( + "braintree:paypalClientId", + configuration.googlePayPayPalClientId + ) + .put( + "braintree:metadata", JSONObject().put("source", "client") + .put("integration", braintreeClient.integrationType) + .put("sessionId", braintreeClient.sessionId) + .put("version", googlePayVersion) + .put("platform", "android").toString() + ) + ) + } catch (ignored: JSONException) { + } + + return json + } + + private fun setGooglePayRequestDefaults( + configuration: Configuration, + authorization: Authorization, + request: GooglePayRequest + ) { + if (request.getAllowedPaymentMethod(CARD_PAYMENT_TYPE) == null) { + request.setAllowedPaymentMethod( + CARD_PAYMENT_TYPE, + buildCardPaymentMethodParameters(configuration, request) + ) + } + + if (request.getTokenizationSpecificationForType(CARD_PAYMENT_TYPE) == null) { + request.setTokenizationSpecificationForType( + "CARD", + buildCardTokenizationSpecification(configuration, authorization) + ) + } + + val googlePayCanProcessPayPal = request.isPayPalEnabled && + !TextUtils.isEmpty(configuration.googlePayPayPalClientId) + + if (googlePayCanProcessPayPal) { + if (request.getAllowedPaymentMethod("PAYPAL") == null) { + request.setAllowedPaymentMethod( + PAYPAL_PAYMENT_TYPE, + buildPayPalPaymentMethodParameters(configuration) + ) + } + + if (request.getTokenizationSpecificationForType(PAYPAL_PAYMENT_TYPE) == null) { + request.setTokenizationSpecificationForType( + "PAYPAL", + buildPayPalTokenizationSpecification(configuration) + ) + } + } + + 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 + ) { + callback.onGooglePayPaymentAuthRequest(request) + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_SUCCEEDED) + } + + private fun callbackPaymentRequestFailure( + request: GooglePayPaymentAuthRequest.Failure, + callback: GooglePayPaymentAuthRequestCallback + ) { + callback.onGooglePayPaymentAuthRequest(request) + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED) + } + + private fun callbackTokenizeSuccess( + result: GooglePayResult.Success, + callback: GooglePayTokenizeCallback + ) { + callback.onGooglePayResult(result) + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_SUCCEEDED) + } + + private fun callbackTokenizeCancel(callback: GooglePayTokenizeCallback) { + callback.onGooglePayResult(GooglePayResult.Cancel) + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_SHEET_CANCELED) + } + + private fun callbackTokenizeFailure( + result: GooglePayResult.Failure, + callback: GooglePayTokenizeCallback + ) { + callback.onGooglePayResult(result) + braintreeClient.sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_FAILED) + } + + 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 VISA_NETWORK = "visa" + private const val MASTERCARD_NETWORK = "mastercard" + private const val AMEX_NETWORK = "amex" + private const val DISCOVER_NETWORK = "discover" + private const val ELO_NETWORK = "elo" + + private const val CARD_PAYMENT_TYPE = "CARD" + private const val PAYPAL_PAYMENT_TYPE = "PAYPAL" + } +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.java deleted file mode 100644 index b53b0bee9e..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.braintreepayments.api.core.BraintreeException; -import com.google.android.gms.common.api.Status; - -/** - * Error class thrown when a Google Pay exception is encountered. - */ -public class GooglePayException extends BraintreeException implements Parcelable { - - private final Status status; - - GooglePayException(String message, Status status) { - super(message); - this.status = status; - } - - /** - * Get the {@link Status} object that contains more details about the error and how to resolve it. - * - * @return {@link Status} - */ - public Status getStatus() { - return status; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(getMessage()); - dest.writeParcelable(status, 0); - } - - protected GooglePayException(Parcel in) { - super(in.readString()); - status = in.readParcelable(Status.class.getClassLoader()); - } - - public static final Creator CREATOR = new Creator() { - @Override - public GooglePayException createFromParcel(Parcel in) { - return new GooglePayException(in); - } - - @Override - public GooglePayException[] newArray(int size) { - return new GooglePayException[size]; - } - }; -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.kt new file mode 100644 index 0000000000..a7a102ba9d --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayException.kt @@ -0,0 +1,19 @@ +package com.braintreepayments.api.googlepay + +import android.os.Parcelable +import com.braintreepayments.api.core.BraintreeException +import com.google.android.gms.common.api.Status +import kotlinx.parcelize.Parcelize + +/** + * + * Error class thrown when a Google Pay exception is encountered. + * + * @property message Human readable top level summary of the error. + * @property status The object that contains more details about the error and how to resolve it. + */ +@Parcelize +internal class GooglePayException( + override val message: String? = null, + val status: Status? = null, +) : BraintreeException(), Parcelable diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.java deleted file mode 100644 index b064b7914b..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.content.Context; - -import com.braintreepayments.api.core.Configuration; -import com.google.android.gms.common.api.ApiException; -import com.google.android.gms.wallet.IsReadyToPayRequest; -import com.google.android.gms.wallet.PaymentsClient; -import com.google.android.gms.wallet.Wallet; -import com.google.android.gms.wallet.WalletConstants; - -class GooglePayInternalClient { - - void isReadyToPay(Context context, Configuration configuration, IsReadyToPayRequest isReadyToPayRequest, final GooglePayIsReadyToPayCallback callback) { - PaymentsClient paymentsClient = Wallet.getPaymentsClient(context, - new Wallet.WalletOptions.Builder() - .setEnvironment(getGooglePayEnvironment(configuration)) - .build()); - paymentsClient.isReadyToPay(isReadyToPayRequest).addOnCompleteListener(task -> { - try { - Boolean isReady = task.getResult(ApiException.class); - if (isReady) { - callback.onGooglePayReadinessResult(GooglePayReadinessResult.ReadyToPay.INSTANCE); - } else { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(null)); - } - } catch (ApiException e) { - callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(e)); - } - }); - } - - int getGooglePayEnvironment(Configuration configuration) { - if ("production".equals(configuration.getGooglePayEnvironment())) { - return WalletConstants.ENVIRONMENT_PRODUCTION; - } else { - return WalletConstants.ENVIRONMENT_TEST; - } - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt new file mode 100644 index 0000000000..4946a144f1 --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayInternalClient.kt @@ -0,0 +1,48 @@ +package com.braintreepayments.api.googlepay + +import android.content.Context +import com.braintreepayments.api.core.Configuration +import com.braintreepayments.api.googlepay.GooglePayReadinessResult.NotReadyToPay +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.Wallet +import com.google.android.gms.wallet.Wallet.WalletOptions +import com.google.android.gms.wallet.WalletConstants + +internal class GooglePayInternalClient { + fun isReadyToPay( + context: Context, + configuration: Configuration, + isReadyToPayRequest: IsReadyToPayRequest, + callback: GooglePayIsReadyToPayCallback + ) { + val paymentsClient = Wallet.getPaymentsClient( + context, + WalletOptions.Builder() + .setEnvironment(getGooglePayEnvironment(configuration)) + .build() + ) + paymentsClient.isReadyToPay(isReadyToPayRequest) + .addOnCompleteListener { task: Task -> + try { + val isReady = task.getResult(ApiException::class.java) + if (isReady) { + callback.onGooglePayReadinessResult(GooglePayReadinessResult.ReadyToPay) + } else { + callback.onGooglePayReadinessResult(NotReadyToPay(null)) + } + } catch (e: ApiException) { + callback.onGooglePayReadinessResult(NotReadyToPay(e)) + } + } + } + + private fun getGooglePayEnvironment(configuration: Configuration): Int { + return if ("production" == configuration.googlePayEnvironment) { + WalletConstants.ENVIRONMENT_PRODUCTION + } else { + WalletConstants.ENVIRONMENT_TEST + } + } +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.java deleted file mode 100644 index 398d355fd9..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import androidx.activity.ComponentActivity; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.ActivityResultRegistry; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.LifecycleOwner; - -/** - * Responsible for launching the Google Pay payment sheet - */ -public class GooglePayLauncher { - - @VisibleForTesting - ActivityResultLauncher activityLauncher; - - private static final String GOOGLE_PAY_RESULT = "com.braintreepayments.api.GooglePay.RESULT"; - - /** - * Used to launch the Google Pay payment sheet from within an Android Fragment. This class must be - * instantiated before the Fragment is created. - * - * @param fragment the Android Fragment from which you will launch the Google Pay payment sheet - * @param callback a {@link GooglePayLauncherCallback} to receive the result of the Google Pay - * payment flow - */ - public GooglePayLauncher(@NonNull Fragment fragment, - @NonNull GooglePayLauncherCallback callback) { - this(fragment.getActivity().getActivityResultRegistry(), fragment.getViewLifecycleOwner(), - callback); - } - - /** - * Used to launch the Google Pay payment sheet from within an Android Activity. This class must be - * instantiated before the Activity is created. - * - * @param activity the Android Activity from which you will launch the Google Pay payment sheet - * @param callback a {@link GooglePayLauncherCallback} to receive the result of the Google Pay - * payment flow - */ - public GooglePayLauncher(@NonNull ComponentActivity activity, - @NonNull GooglePayLauncherCallback callback) { - this(activity.getActivityResultRegistry(), activity, callback); - } - - @VisibleForTesting - GooglePayLauncher(ActivityResultRegistry registry, LifecycleOwner lifecycleOwner, - GooglePayLauncherCallback callback) { - activityLauncher = registry.register(GOOGLE_PAY_RESULT, lifecycleOwner, - new GooglePayActivityResultContract(), callback::onGooglePayLauncherResult); - } - - /** - * Launches the Google Pay payment sheet. This method cannot be called until the lifecycle of - * the Fragment or Activity used to instantiate your {@link GooglePayLauncher} has reached the - * CREATED state. - * - * @param googlePayPaymentAuthRequestParams the {@link GooglePayPaymentAuthRequestParams} - * received from invoking - * {@link GooglePayClient#createPaymentAuthRequest(GooglePayRequest, GooglePayPaymentAuthRequestCallback)} - */ - public void launch(GooglePayPaymentAuthRequestParams googlePayPaymentAuthRequestParams) { - activityLauncher.launch(googlePayPaymentAuthRequestParams); - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt new file mode 100644 index 0000000000..2ba63f512d --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayLauncher.kt @@ -0,0 +1,73 @@ +package com.braintreepayments.api.googlepay + +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.ActivityResultRegistry +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner + +/** + * Responsible for launching the Google Pay payment sheet + */ +class GooglePayLauncher @VisibleForTesting internal constructor( + registry: ActivityResultRegistry, + lifecycleOwner: LifecycleOwner, + callback: GooglePayLauncherCallback +) { + + private val activityLauncher: ActivityResultLauncher = registry.register( + GOOGLE_PAY_RESULT, lifecycleOwner, + GooglePayActivityResultContract() + ) { googlePayPaymentAuthResult: GooglePayPaymentAuthResult? -> + callback.onGooglePayLauncherResult( + googlePayPaymentAuthResult + ) + } + + /** + * Used to launch the Google Pay payment sheet from within an Android Fragment. This class must be + * instantiated before the Fragment is created. + * + * @param fragment the Android Fragment from which you will launch the Google Pay payment sheet + * @param callback a [GooglePayLauncherCallback] to receive the result of the Google Pay + * payment flow + */ + constructor( + fragment: Fragment, + callback: GooglePayLauncherCallback + ) : this( + fragment.requireActivity().activityResultRegistry, fragment.viewLifecycleOwner, + callback + ) + + /** + * Used to launch the Google Pay payment sheet from within an Android Activity. This class must be + * instantiated before the Activity is created. + * + * @param activity the Android Activity from which you will launch the Google Pay payment sheet + * @param callback a [GooglePayLauncherCallback] to receive the result of the Google Pay + * payment flow + */ + constructor( + activity: ComponentActivity, + callback: GooglePayLauncherCallback + ) : this(activity.activityResultRegistry, activity, callback) + + /** + * Launches the Google Pay payment sheet. This method cannot be called until the lifecycle of + * the Fragment or Activity used to instantiate your [GooglePayLauncher] has reached the + * CREATED state. + * + * @param googlePayPaymentAuthRequestParams the [GooglePayPaymentAuthRequestParams] + * received from invoking + * [GooglePayClient.createPaymentAuthRequest] + */ + fun launch(googlePayPaymentAuthRequestParams: GooglePayPaymentAuthRequestParams) { + activityLauncher.launch(googlePayPaymentAuthRequestParams) + } + + companion object { + private const val GOOGLE_PAY_RESULT = "com.braintreepayments.api.GooglePay.RESULT" + } +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.java deleted file mode 100644 index b350a795d1..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import androidx.annotation.NonNull; - -import com.google.android.gms.wallet.PaymentDataRequest; - -/** - * Used to request Google Pay payment authorization via - * {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequestParams)} - */ -public class GooglePayPaymentAuthRequestParams { - - private final int googlePayEnvironment; - private final PaymentDataRequest paymentDataRequest; - - GooglePayPaymentAuthRequestParams(int googlePayEnvironment, @NonNull PaymentDataRequest paymentDataRequest) { - this.googlePayEnvironment = googlePayEnvironment; - this.paymentDataRequest = paymentDataRequest; - } - - int getGooglePayEnvironment() { - return googlePayEnvironment; - } - - PaymentDataRequest getPaymentDataRequest() { - return paymentDataRequest; - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.kt new file mode 100644 index 0000000000..1a41951147 --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthRequestParams.kt @@ -0,0 +1,12 @@ +package com.braintreepayments.api.googlepay + +import com.google.android.gms.wallet.PaymentDataRequest + +/** + * Used to request Google Pay payment authorization via + * [GooglePayLauncher.launch] + */ +class GooglePayPaymentAuthRequestParams internal constructor( + val googlePayEnvironment: Int, + val paymentDataRequest: PaymentDataRequest +) diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.java deleted file mode 100644 index 8610332ecd..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import androidx.annotation.Nullable; - -import com.google.android.gms.wallet.PaymentData; - -/** - * Result returned from the callback used to instantiate {@link GooglePayLauncher} that should be - * passed to {@link GooglePayClient#tokenize(GooglePayPaymentAuthResult, GooglePayTokenizeCallback)} - */ -public class GooglePayPaymentAuthResult { - - private final PaymentData paymentData; - private final Exception error; - - GooglePayPaymentAuthResult(@Nullable PaymentData paymentData, @Nullable Exception error) { - this.paymentData = paymentData; - this.error = error; - } - - PaymentData getPaymentData() { - return paymentData; - } - - Exception getError() { - return error; - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.kt new file mode 100644 index 0000000000..12b37bdb52 --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayPaymentAuthResult.kt @@ -0,0 +1,12 @@ +package com.braintreepayments.api.googlepay + +import com.google.android.gms.wallet.PaymentData + +/** + * Result returned from the callback used to instantiate [GooglePayLauncher] that should be + * passed to [GooglePayClient.tokenize] + */ +class GooglePayPaymentAuthResult internal constructor( + val paymentData: PaymentData?, + val error: Exception? +) diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.java deleted file mode 100644 index b5451d8584..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.java +++ /dev/null @@ -1,538 +0,0 @@ -package com.braintreepayments.api.googlepay; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.gms.wallet.ShippingAddressRequirements; -import com.google.android.gms.wallet.TransactionInfo; -import com.google.android.gms.wallet.WalletConstants; -import com.google.android.gms.wallet.WalletConstants.BillingAddressFormat; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Represents the parameters that are needed to use the Google Pay API. - */ -public class GooglePayRequest implements Parcelable { - - // NEXT_MAJOR_VERSION: allow merchants to set transaction info params individually and build - // JSON object under the hood - private TransactionInfo transactionInfo; - private boolean emailRequired; - private boolean phoneNumberRequired; - private boolean billingAddressRequired; - private int billingAddressFormat; - private boolean shippingAddressRequired; - // NEXT_MAJOR_VERSION: allow merchants to set shipping address requirements params individually - // and build JSON object under the hood - private ShippingAddressRequirements shippingAddressRequirements; - private boolean allowPrepaidCards; - private boolean payPalEnabled = true; - private final HashMap allowedPaymentMethods = new HashMap<>(); - private final HashMap tokenizationSpecifications = new HashMap<>(); - private final HashMap allowedAuthMethods = new HashMap<>(); - private final HashMap allowedCardNetworks = new HashMap<>(); - private String environment; - private boolean allowCreditCards = true; - private String googleMerchantName; - private String countryCode; - private String totalPriceLabel; - - public GooglePayRequest() { - } - - /** - * Details and the price of the transaction. Required. - * - * @param transactionInfo See {@link TransactionInfo}. - */ - public void setTransactionInfo(@Nullable TransactionInfo transactionInfo) { - this.transactionInfo = transactionInfo; - } - - /** - * Optional. - * - * @param emailRequired {@code true} if the buyer's email address is required to be returned, {@code false} otherwise. - */ - public void setEmailRequired(boolean emailRequired) { - this.emailRequired = emailRequired; - } - - /** - * Optional. - * - * @param phoneNumberRequired {@code true} if the buyer's phone number is required to be returned as part of the - * billing address and shipping address, {@code false} otherwise. - */ - public void setPhoneNumberRequired(boolean phoneNumberRequired) { - this.phoneNumberRequired = phoneNumberRequired; - } - - /** - * Optional. - * - * @param billingAddressRequired {@code true} if the buyer's billing address is required to be returned, - * {@code false} otherwise. - */ - public void setBillingAddressRequired(boolean billingAddressRequired) { - this.billingAddressRequired = billingAddressRequired; - } - - /** - * Optional. - * - * @param billingAddressFormat the billing address format to return. {@link BillingAddressFormat} - */ - public void setBillingAddressFormat(@BillingAddressFormat int billingAddressFormat) { - this.billingAddressFormat = billingAddressFormat; - } - - /** - * Optional. - * - * @param shippingAddressRequired {@code true} if the buyer's shipping address is required to be returned, - * {@code false} otherwise. - */ - public void setShippingAddressRequired(boolean shippingAddressRequired) { - this.shippingAddressRequired = shippingAddressRequired; - } - - /** - * Optional. - * - * @param shippingAddressRequirements the shipping address requirements. {@link ShippingAddressRequirements} - */ - public void setShippingAddressRequirements(@Nullable ShippingAddressRequirements shippingAddressRequirements) { - this.shippingAddressRequirements = shippingAddressRequirements; - } - - /** - * Optional. - * - * @param allowPrepaidCards {@code true} prepaid cards are allowed, {@code false} otherwise. - */ - public void setAllowPrepaidCards(boolean allowPrepaidCards) { - this.allowPrepaidCards = allowPrepaidCards; - } - - /** - * Defines if PayPal should be an available payment method in Google Pay. - * Defaults to {@code true}. - * - * @param enablePayPal {@code true} by default. Allows PayPal to be a payment method in Google Pay. - */ - public void setPayPalEnabled(boolean enablePayPal) { - payPalEnabled = enablePayPal; - } - - /** - * Simple wrapper to assign given parameters to specified paymentMethod - * - * @param paymentMethodType The paymentMethod to add to - * @param parameters Parameters to assign to the paymentMethod - */ - public void setAllowedPaymentMethod(@NonNull String paymentMethodType, @NonNull JSONObject parameters) { - allowedPaymentMethods.put(paymentMethodType, parameters); - } - - /** - * Simple wrapper to configure the GooglePayRequest's tokenizationSpecification - * - * @param paymentMethodType The paymentMethod to attached tokenizationSpecification parameters to - * @param parameters The tokenizationSpecification parameters to attach - */ - public void setTokenizationSpecificationForType(@NonNull String paymentMethodType, @NonNull JSONObject parameters) { - tokenizationSpecifications.put(paymentMethodType, parameters); - } - - /** - * Simple wrapper to configure the GooglePayRequest's allowedAuthMethods - * - * @param paymentMethodType the paymentMethod to attach allowedAuthMethods to - * @param authMethods the authMethods to allow the paymentMethodType to transact with - */ - public void setAllowedAuthMethods(@NonNull String paymentMethodType, @NonNull JSONArray authMethods) { - allowedAuthMethods.put(paymentMethodType, authMethods); - } - - /** - * Simple wrapper to configure the GooglePayRequest's cardNetworks - * - * @param paymentMethodType the paymentMethod to attach cardNetworks to - * @param cardNetworks the cardNetworks to allow the paymentMethodType to transact with - */ - public void setAllowedCardNetworks(@NonNull String paymentMethodType, @NonNull JSONArray cardNetworks) { - allowedCardNetworks.put(paymentMethodType, cardNetworks); - } - - /** - * Optional. - * - * @param merchantName The merchant name that will be presented in Google Pay - */ - public void setGoogleMerchantName(@Nullable String merchantName) { - googleMerchantName = merchantName; - } - - public void setEnvironment(@Nullable String environment) { - this.environment = "PRODUCTION".equals(environment.toUpperCase()) ? "PRODUCTION" : "TEST"; - } - - /** - * ISO 3166-1 alpha-2 country code where the transaction is processed. This is required for - * merchants based in European Economic Area (EEA) countries. - *

- * NOTE: to support Elo cards, country code must be set to "BR" - * - * @param countryCode The country code where the transaction is processed - */ - public void setCountryCode(@Nullable String countryCode) { - this.countryCode = countryCode; - } - - /** - * Optional. - * - * @param allowCreditCards Set to {@code false} if you don't support credit cards. - * Defaults to {@code true}. - */ - public void setAllowCreditCards(boolean allowCreditCards) { - this.allowCreditCards = allowCreditCards; - } - - /** - * Optional - * - * @param totalPriceLabel Custom label for the total price within the display items - */ - public void setTotalPriceLabel(@Nullable String totalPriceLabel) { - this.totalPriceLabel = totalPriceLabel; - } - - /** - * Assemble all declared parts of a GooglePayRequest to a JSON string - * for use in making requests against Google - * - * @return String - */ - public String toJson() { - JSONObject transactionInfoJson = new JSONObject(); - TransactionInfo transactionInfo = getTransactionInfo(); - JSONArray allowedPaymentMethods = new JSONArray(); - JSONObject shippingAddressParameters = new JSONObject(); - ArrayList allowedCountryCodeList; - - if (isShippingAddressRequired()) { - allowedCountryCodeList = shippingAddressRequirements.getAllowedCountryCodes(); - - if (allowedCountryCodeList != null && allowedCountryCodeList.size() > 0) { - try { - shippingAddressParameters.put("allowedCountryCodes", new JSONArray(allowedCountryCodeList)); - } catch (JSONException ignored) { - } - } - try { - shippingAddressParameters.put("phoneNumberRequired", isPhoneNumberRequired()); - } catch (JSONException ignored) { - } - } - - try { - String totalPriceStatus = totalPriceStatusToString(); - transactionInfoJson - .put("totalPriceStatus", totalPriceStatus) - .put("totalPrice", transactionInfo.getTotalPrice()) - .put("currencyCode", transactionInfo.getCurrencyCode()); - - if (countryCode != null) { - transactionInfoJson.put("countryCode", countryCode); - } - - if (totalPriceLabel != null) { - transactionInfoJson.put("totalPriceLabel", totalPriceLabel); - } - - } catch (JSONException ignored) { - } - - for (Map.Entry pm : this.allowedPaymentMethods.entrySet()) { - try { - JSONObject paymentMethod = new JSONObject() - .put("type", pm.getKey()) - .put("parameters", pm.getValue()) - .put("tokenizationSpecification", tokenizationSpecifications.get(pm.getKey())); - - if ("CARD".equals(pm.getKey())) { - JSONObject paymentMethodParams = paymentMethod.getJSONObject("parameters"); - paymentMethodParams - .put("billingAddressRequired", isBillingAddressRequired()) - .put("allowPrepaidCards", getAllowPrepaidCards()) - .put("allowCreditCards", isCreditCardsAllowed()); - try { - JSONObject billingAddressParameters = (JSONObject) pm.getValue().get( - "billingAddressParameters"); - paymentMethodParams - .put("billingAddressParameters", billingAddressParameters); - } catch (JSONException ignored) { - if (isBillingAddressRequired()) { - paymentMethodParams - .put("billingAddressParameters", new JSONObject() - .put("format", billingAddressFormatToString()) - .put("phoneNumberRequired", isPhoneNumberRequired())); - } - } - } - - allowedPaymentMethods.put(paymentMethod); - } catch (JSONException ignored) { - } - } - - JSONObject merchantInfo = new JSONObject(); - - try { - if (!TextUtils.isEmpty(getGoogleMerchantName())) { - merchantInfo.put("merchantName", getGoogleMerchantName()); - } - } catch (JSONException ignored) { - } - - JSONObject json = new JSONObject(); - - try { - json - .put("apiVersion", 2) - .put("apiVersionMinor", 0) - .put("allowedPaymentMethods", allowedPaymentMethods) - .put("emailRequired", isEmailRequired()) - .put("shippingAddressRequired", isShippingAddressRequired()) - .put("environment", environment) - .put("merchantInfo", merchantInfo) - .put("transactionInfo", transactionInfoJson); - - if (isShippingAddressRequired()) { - json.put("shippingAddressParameters", shippingAddressParameters); - } - } catch (JSONException ignored) { - } - - return json.toString(); - } - - private String totalPriceStatusToString() { - switch (getTransactionInfo().getTotalPriceStatus()) { - case WalletConstants.TOTAL_PRICE_STATUS_NOT_CURRENTLY_KNOWN: - return "NOT_CURRENTLY_KNOWN"; - case WalletConstants.TOTAL_PRICE_STATUS_ESTIMATED: - return "ESTIMATED"; - case WalletConstants.TOTAL_PRICE_STATUS_FINAL: - default: - return "FINAL"; - } - } - - public String billingAddressFormatToString() { - String format = "MIN"; - if (billingAddressFormat == WalletConstants.BILLING_ADDRESS_FORMAT_FULL) { - format = "FULL"; - } - return format; - } - - /** - * Details and the price of the transaction. Required. - * - * @return See {@link TransactionInfo}. - */ - public TransactionInfo getTransactionInfo() { - return transactionInfo; - } - - /** - * @return If the buyer's email address is required to be returned. - */ - public boolean isEmailRequired() { - return emailRequired; - } - - /** - * @return If the buyer's phone number is required to be returned as part of the - * billing address and shipping address. - */ - public boolean isPhoneNumberRequired() { - return phoneNumberRequired; - } - - /** - * @return If the buyer's billing address is required to be returned. - */ - public boolean isBillingAddressRequired() { - return billingAddressRequired; - } - - /** - * @return If the buyer's billing address is required to be returned. - */ - @BillingAddressFormat - public int getBillingAddressFormat() { - return billingAddressFormat; - } - - /** - * @return If the buyer's shipping address is required to be returned. - */ - public boolean isShippingAddressRequired() { - return shippingAddressRequired; - } - - /** - * @return The shipping address requirements. See {@link ShippingAddressRequirements}. - */ - @Nullable - public ShippingAddressRequirements getShippingAddressRequirements() { - return shippingAddressRequirements; - } - - /** - * @return If prepaid cards are allowed. - */ - public boolean getAllowPrepaidCards() { - return allowPrepaidCards; - } - - /** - * @return If PayPal should be an available payment method in Google Pay. - */ - public boolean isPayPalEnabled() { - return payPalEnabled; - } - - /** - * @return Allowed payment methods for a given payment method type. - */ - @Nullable - public JSONObject getAllowedPaymentMethod(String type) { - return allowedPaymentMethods.get(type); - } - - /** - * @return Tokenization specification for a given payment method type. - */ - @Nullable - public JSONObject getTokenizationSpecificationForType(String type) { - return tokenizationSpecifications.get(type); - } - - /** - * @return Allowed authentication methods for a given payment method type. - */ - @Nullable - public JSONArray getAllowedAuthMethodsForType(String type) { - return allowedAuthMethods.get(type); - } - - /** - * @return Allowed card networks for a given payment method type. - */ - @Nullable - public JSONArray getAllowedCardNetworksForType(String type) { - return allowedCardNetworks.get(type); - } - - @Nullable - public String getEnvironment() { - return environment; - } - - - /** - * @return The merchant name that will be presented in Google Pay. - */ - @Nullable - public String getGoogleMerchantName() { - return googleMerchantName; - } - - /** - * @return The country code where the transaction is processed. - */ - @Nullable - public String getCountryCode() { - return countryCode; - } - - /** - * @return If credit cards are allowed. - */ - public boolean isCreditCardsAllowed() { - return allowCreditCards; - } - - @Nullable - public String getTotalPriceLabel() { - return totalPriceLabel; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(transactionInfo, flags); - dest.writeByte((byte) (emailRequired ? 1 : 0)); - dest.writeByte((byte) (phoneNumberRequired ? 1 : 0)); - dest.writeByte((byte) (billingAddressRequired ? 1 : 0)); - dest.writeInt(billingAddressFormat); - dest.writeByte((byte) (shippingAddressRequired ? 1 : 0)); - dest.writeParcelable(shippingAddressRequirements, flags); - dest.writeByte((byte) (allowPrepaidCards ? 1 : 0)); - dest.writeByte((byte) (payPalEnabled ? 1 : 0)); - dest.writeString(environment); - dest.writeString(googleMerchantName); - dest.writeString(countryCode); - dest.writeByte((byte) (allowCreditCards ? 1 : 0)); - dest.writeString(totalPriceLabel); - } - - GooglePayRequest(Parcel in) { - transactionInfo = in.readParcelable(TransactionInfo.class.getClassLoader()); - emailRequired = in.readByte() != 0; - phoneNumberRequired = in.readByte() != 0; - billingAddressRequired = in.readByte() != 0; - billingAddressFormat = in.readInt(); - shippingAddressRequired = in.readByte() != 0; - shippingAddressRequirements = in.readParcelable(ShippingAddressRequirements.class.getClassLoader()); - allowPrepaidCards = in.readByte() != 0; - payPalEnabled = in.readByte() != 0; - environment = in.readString(); - googleMerchantName = in.readString(); - countryCode = in.readString(); - allowCreditCards = in.readByte() != 0; - totalPriceLabel = in.readString(); - } - - public static final Creator CREATOR = new Creator() { - @Override - public GooglePayRequest createFromParcel(Parcel in) { - return new GooglePayRequest(in); - } - - @Override - public GooglePayRequest[] newArray(int size) { - return new GooglePayRequest[size]; - } - }; -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.kt new file mode 100644 index 0000000000..047a9c62c0 --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayRequest.kt @@ -0,0 +1,316 @@ +package com.braintreepayments.api.googlepay + +import android.os.Parcelable +import android.text.TextUtils +import com.google.android.gms.wallet.ShippingAddressRequirements +import com.google.android.gms.wallet.TransactionInfo +import com.google.android.gms.wallet.WalletConstants +import com.google.android.gms.wallet.WalletConstants.BillingAddressFormat +import kotlinx.parcelize.Parcelize +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.util.Locale + +/** + * Represents the parameters that are needed to use the Google Pay API. + * + * Details and the price of the transaction. Required. + * @property transactionInfo See [TransactionInfo]. + * + * Optional. + * @property emailRequired `true` if the buyer's email address is required to be returned, `false` otherwise. + * + * Optional. + * @property phoneNumberRequired `true` if the buyer's phone number is required to be returned as part of the + * billing address and shipping address, `false` otherwise. + * + * Optional. + * @property billingAddressRequired `true` if the buyer's billing address is required to be returned, + * false` otherwise. + * + * Optional. + * @property billingAddressFormat the billing address format to return. [BillingAddressFormat] + * + * Optional. + * @property shippingAddressRequired `true` if the buyer's shipping address is required to be returned, + * `false` otherwise. + * + * Optional. + * @property shippingAddressRequirements the shipping address requirements. [ShippingAddressRequirements] + * + * Optional. + * @property allowPrepaidCards `true` prepaid cards are allowed, `false` otherwise. + * + * Defines if PayPal should be an available payment method in Google Pay. + * Defaults to `true`. + * @property enablePayPal `true` by default. Allows PayPal to be a payment method in Google Pay. + * + * Optional. + * @property merchantName The merchant name that will be presented in Google Pay + * + * ISO 3166-1 alpha-2 country code where the transaction is processed. This is required for + * merchants based in European Economic Area (EEA) countries. + * NOTE: to support Elo cards, country code must be set to "BR" + * @property countryCode The country code where the transaction is processed + * + * Optional + * @property totalPriceLabel Custom label for the total price within the display items + */ +@Suppress("TooManyFunctions") +@Parcelize +class GooglePayRequest( + // NEXT_MAJOR_VERSION: allow merchants to set transaction info params individually and build + // JSON object under the hood + var transactionInfo: TransactionInfo? = null, + var isEmailRequired: Boolean = false, + var isPhoneNumberRequired: Boolean = false, + var isBillingAddressRequired: Boolean = false, + @BillingAddressFormat + var billingAddressFormat: Int = 0, + var isShippingAddressRequired: Boolean = false, + // NEXT_MAJOR_VERSION: allow merchants to set shipping address requirements params individually + // and build JSON object under the hood + var shippingAddressRequirements: ShippingAddressRequirements? = null, + var allowPrepaidCards: Boolean = false, + var isPayPalEnabled: Boolean = true, + var googleMerchantName: String? = null, + var countryCode: String? = null, + var totalPriceLabel: String? = null, + var allowCreditCards: Boolean = true, + private var environment: String? = null, + private val allowedPaymentMethods: MutableMap = HashMap(), + private val tokenizationSpecifications: MutableMap = HashMap(), + private val allowedAuthMethods: MutableMap = HashMap(), + private val allowedCardNetworks: MutableMap = HashMap() +) : Parcelable { + + fun setEnvironment(environment: String?) { + this.environment = + if ("PRODUCTION" == environment?.uppercase(Locale.getDefault())) "PRODUCTION" else "TEST" + } + + fun getEnvironment(): String? { + return environment + } + + /** + * Simple wrapper to assign given parameters to specified paymentMethod + * + * @param paymentMethodType The paymentMethod to add to + * @param parameters Parameters to assign to the paymentMethod + */ + fun setAllowedPaymentMethod(paymentMethodType: String, parameters: JSONObject) { + allowedPaymentMethods[paymentMethodType] = parameters.toString() + } + + /** + * Simple wrapper to configure the GooglePayRequest's tokenizationSpecification + * + * @param paymentMethodType The paymentMethod to attached tokenizationSpecification parameters to + * @param parameters The tokenizationSpecification parameters to attach + */ + fun setTokenizationSpecificationForType(paymentMethodType: String, parameters: JSONObject) { + tokenizationSpecifications[paymentMethodType] = parameters.toString() + } + + /** + * Simple wrapper to configure the GooglePayRequest's allowedAuthMethods + * + * @param paymentMethodType the paymentMethod to attach allowedAuthMethods to + * @param authMethods the authMethods to allow the paymentMethodType to transact with + */ + fun setAllowedAuthMethods(paymentMethodType: String, authMethods: JSONArray) { + allowedAuthMethods[paymentMethodType] = authMethods.toString() + } + + /** + * Simple wrapper to configure the GooglePayRequest's cardNetworks + * + * @param paymentMethodType the paymentMethod to attach cardNetworks to + * @param cardNetworks the cardNetworks to allow the paymentMethodType to transact with + */ + fun setAllowedCardNetworks(paymentMethodType: String, cardNetworks: JSONArray) { + allowedCardNetworks[paymentMethodType] = cardNetworks.toString() + } + + /** + * Assemble all declared parts of a GooglePayRequest to a JSON string + * for use in making requests against Google + * + * @return String + */ + + @SuppressWarnings("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth") + fun toJson(): String { + val transactionInfoJson = JSONObject() + val transactionInfo = transactionInfo + val allowedPaymentMethods = JSONArray() + val shippingAddressParameters = JSONObject() + val allowedCountryCodeList: ArrayList? + + if (isShippingAddressRequired) { + allowedCountryCodeList = shippingAddressRequirements?.allowedCountryCodes + + if (allowedCountryCodeList != null && allowedCountryCodeList.size > 0) { + try { + shippingAddressParameters.put( + "allowedCountryCodes", + JSONArray(allowedCountryCodeList) + ) + } catch (ignored: JSONException) { + } + } + try { + shippingAddressParameters.put("phoneNumberRequired", isPhoneNumberRequired) + } catch (ignored: JSONException) { + } + } + + try { + val totalPriceStatus = totalPriceStatusToString() + transactionInfoJson + .put("totalPriceStatus", totalPriceStatus) + + if (transactionInfo != null) { + transactionInfoJson.put("totalPrice", transactionInfo.totalPrice) + transactionInfoJson.put("currencyCode", transactionInfo.currencyCode) + } + + countryCode?.let { + transactionInfoJson.put("countryCode", it) + } + + totalPriceLabel?.let { + transactionInfoJson.put("totalPriceLabel", it) + } + } catch (ignored: JSONException) { + } + + for ((key, value) in this.allowedPaymentMethods) { + try { + val paymentMethod = JSONObject() + .put("type", key) + .put("parameters", JSONObject(value)) + val tokenSpec = tokenizationSpecifications[key] + if (tokenSpec != null) { + paymentMethod.put("tokenizationSpecification", JSONObject(tokenSpec)) + } + + if ("CARD" == key) { + val paymentMethodParams = paymentMethod.getJSONObject("parameters") + paymentMethodParams + .put("billingAddressRequired", isBillingAddressRequired) + .put("allowPrepaidCards", allowPrepaidCards) + .put("allowCreditCards", allowCreditCards) + try { + val billingAddressParameters = + JSONObject(value)["billingAddressParameters"] + paymentMethodParams + .put("billingAddressParameters", billingAddressParameters) + } catch (ignored: JSONException) { + if (isBillingAddressRequired) { + paymentMethodParams + .put( + "billingAddressParameters", JSONObject() + .put("format", billingAddressFormatToString()) + .put("phoneNumberRequired", isPhoneNumberRequired) + ) + } + } + } + + allowedPaymentMethods.put(paymentMethod) + } catch (ignored: JSONException) { + } + } + + val merchantInfo = JSONObject() + + try { + if (!TextUtils.isEmpty(googleMerchantName)) { + merchantInfo.put("merchantName", googleMerchantName) + } + } catch (ignored: JSONException) { + } + + val json = JSONObject() + + try { + json + .put("apiVersion", 2) + .put("apiVersionMinor", 0) + .put("allowedPaymentMethods", allowedPaymentMethods) + .put("emailRequired", isEmailRequired) + .put("shippingAddressRequired", isShippingAddressRequired) + .put("environment", environment) + .put("merchantInfo", merchantInfo) + .put("transactionInfo", transactionInfoJson) + + if (isShippingAddressRequired) { + json.put("shippingAddressParameters", shippingAddressParameters) + } + } catch (ignored: JSONException) { + } + + return json.toString() + } + + private fun totalPriceStatusToString(): String { + + return transactionInfo?.let { + when (it.totalPriceStatus) { + WalletConstants.TOTAL_PRICE_STATUS_NOT_CURRENTLY_KNOWN -> "NOT_CURRENTLY_KNOWN" + WalletConstants.TOTAL_PRICE_STATUS_ESTIMATED -> "ESTIMATED" + WalletConstants.TOTAL_PRICE_STATUS_FINAL -> "FINAL" + else -> "FINAL" + } + } ?: run { + "FINAL" + } + } + + fun billingAddressFormatToString(): String { + var format = "MIN" + if (billingAddressFormat == WalletConstants.BILLING_ADDRESS_FORMAT_FULL) { + format = "FULL" + } + return format + } + + /** + * @return Allowed payment methods for a given payment method type. + */ + fun getAllowedPaymentMethod(type: String): JSONObject? { + return allowedPaymentMethods[type]?.let { + JSONObject(it) + } + } + + /** + * @return Tokenization specification for a given payment method type. + */ + fun getTokenizationSpecificationForType(type: String): JSONObject? { + return tokenizationSpecifications[type]?.let { + JSONObject(it) + } + } + + /** + * @return Allowed authentication methods for a given payment method type. + */ + fun getAllowedAuthMethodsForType(type: String): JSONArray? { + return allowedAuthMethods[type]?.let { + JSONArray(it) + } + } + + /** + * @return Allowed card networks for a given payment method type. + */ + fun getAllowedCardNetworksForType(type: String): JSONArray? { + return allowedCardNetworks[type]?.let { + JSONArray(it) + } + } +} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayTokenizationParameters.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayTokenizationParameters.kt index e75e3cf55a..ba3402b7f0 100644 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayTokenizationParameters.kt +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/GooglePayTokenizationParameters.kt @@ -11,7 +11,7 @@ sealed class GooglePayTokenizationParameters { * The request was successfully created */ class Success(val parameters: PaymentMethodTokenizationParameters, - val allowedCardNetworks: Collection + val allowedCardNetworks: Collection ) : GooglePayTokenizationParameters() /** diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.java b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.java deleted file mode 100644 index 9f8b6df737..0000000000 --- a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.braintreepayments.api.googlepay; - -/** - * Optional parameters to use when checking whether Google Pay is supported and set up on the customer's device. - */ -public class ReadyForGooglePayRequest { - - private boolean existingPaymentMethodRequired; - - /** - * If set to true, then the {@link GooglePayClient#isReadyToPay(android.content.Context, ReadyForGooglePayRequest, GooglePayIsReadyToPayCallback)} - * method will call the listener with true if the customer is ready to pay with one or more of your - * supported card networks. - * - * @param existingPaymentMethodRequired Indicates whether the customer must already have at least one payment method from your supported - * card networks in order to be considered ready to pay with Google Pay - */ - public void setExistingPaymentMethodRequired(boolean existingPaymentMethodRequired) { - this.existingPaymentMethodRequired = existingPaymentMethodRequired; - } - - public boolean isExistingPaymentMethodRequired() { - return existingPaymentMethodRequired; - } -} diff --git a/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.kt b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.kt new file mode 100644 index 0000000000..cda81fe28e --- /dev/null +++ b/GooglePay/src/main/java/com/braintreepayments/api/googlepay/ReadyForGooglePayRequest.kt @@ -0,0 +1,17 @@ +package com.braintreepayments.api.googlepay + +/** + * Optional parameters to use when checking whether Google Pay is supported and set up on the customer's device. + */ +class ReadyForGooglePayRequest { + /** + * If set to true, then the [GooglePayClient.isReadyToPay] + * method will call the listener with true if the customer is ready to pay with one or more of your + * supported card networks. + * + * @param existingPaymentMethodRequired Indicates whether the customer must already have + * at least one payment method from your supported card networks in order to be considered + * ready to pay with Google Pay + */ + var isExistingPaymentMethodRequired: Boolean = false +} diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.java b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.java index d04cd54a19..c58f5d525f 100644 --- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.java +++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayClientUnitTest.java @@ -163,35 +163,6 @@ public void isReadyToPay_returnsFalseWhenGooglePayIsNotEnabled() { verify(readyToPayCallback).onGooglePayReadinessResult(any(GooglePayReadinessResult.NotReadyToPay.class)); } - @Test - public void isReadyToPay_whenActivityIsNull_forwardsErrorToCallback() { - Configuration configuration = new TestConfigurationBuilder() - .googlePay(new TestConfigurationBuilder.TestGooglePayConfigurationBuilder().enabled( - true)) - .buildConfiguration(); - - BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(configuration) - .authorizationSuccess(Authorization.fromString(Fixtures.TOKENIZATION_KEY)) - .activityInfo(activityInfo) - .build(); - - GooglePayInternalClient internalGooglePayClient = - new MockGooglePayInternalClientBuilder().build(); - - GooglePayClient sut = new GooglePayClient(braintreeClient, internalGooglePayClient); - sut.isReadyToPay(null, null, readyToPayCallback); - - ArgumentCaptor captor = ArgumentCaptor.forClass(GooglePayReadinessResult.class); - verify(readyToPayCallback).onGooglePayReadinessResult(captor.capture()); - - GooglePayReadinessResult result = captor.getValue(); - assertTrue(result instanceof GooglePayReadinessResult.NotReadyToPay); - Throwable exception = ((GooglePayReadinessResult.NotReadyToPay) result).getError(); - assertTrue(exception instanceof IllegalArgumentException); - assertEquals("Activity cannot be null.", exception.getMessage()); - } - // endregion // region createPaymentAuthRequest @@ -462,8 +433,8 @@ public void createPaymentAuthRequest_sendsAnalyticsEvent() { sut.createPaymentAuthRequest(googlePayRequest, intentDataCallback); InOrder order = inOrder(braintreeClient); - order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_STARTED)); - order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_SUCCEEDED)); + order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_STARTED), any()); + order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_SUCCEEDED), any()); } @Test @@ -492,8 +463,8 @@ public void createPaymentAuthRequest_postsExceptionWhenTransactionInfoIsNull() { sut.createPaymentAuthRequest(googlePayRequest, intentDataCallback); InOrder order = inOrder(braintreeClient); - order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_STARTED)); - order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_FAILED)); + order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_STARTED), any()); + order.verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_FAILED), any()); } @Test @@ -524,7 +495,7 @@ public void createPaymentAuthRequest_whenMerchantNotConfigured_returnsExceptionT assertEquals( "Google Pay is not enabled for your Braintree account, or Google Play Services are not configured correctly.", exception.getMessage()); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_FAILED), any()); } @Test @@ -952,42 +923,6 @@ public void createPaymentAuthRequest_whenConfigurationContainsElo_addsEloAndEloD assertEquals("ELO_DEBIT", allowedCardNetworks.getString(1)); } - @Test - public void createPaymentAuthRequest_whenRequestIsNull_forwardsExceptionToListener() { - Configuration configuration = new TestConfigurationBuilder() - .googlePay(new TestConfigurationBuilder.TestGooglePayConfigurationBuilder() - .environment("sandbox") - .googleAuthorizationFingerprint("google-auth-fingerprint") - .paypalClientId("paypal-client-id-for-google-payment") - .supportedNetworks(new String[]{"visa", "mastercard", "amex", "discover"}) - .enabled(true)) - .withAnalytics() - .buildConfiguration(); - - BraintreeClient braintreeClient = new MockBraintreeClientBuilder() - .configuration(configuration) - .authorizationSuccess(Authorization.fromString("sandbox_tokenization_string")) - .activityInfo(activityInfo) - .build(); - - GooglePayInternalClient internalGooglePayClient = - new MockGooglePayInternalClientBuilder().build(); - - GooglePayClient sut = new GooglePayClient(braintreeClient, internalGooglePayClient); - sut.createPaymentAuthRequest(null, intentDataCallback); - - ArgumentCaptor captor = ArgumentCaptor.forClass( - GooglePayPaymentAuthRequest.class); - verify(intentDataCallback).onGooglePayPaymentAuthRequest(captor.capture()); - - GooglePayPaymentAuthRequest request = captor.getValue(); - assertTrue(request instanceof GooglePayPaymentAuthRequest.Failure); - Exception exception = ((GooglePayPaymentAuthRequest.Failure) request).getError(); - assertTrue(exception instanceof BraintreeException); - assertEquals("Cannot pass null GooglePayRequest to requestPayment", exception.getMessage()); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED); - } - @Test public void createPaymentAuthRequest_whenManifestInvalid_forwardsExceptionToListener() { Configuration configuration = new TestConfigurationBuilder() @@ -1023,7 +958,7 @@ public void createPaymentAuthRequest_whenManifestInvalid_forwardsExceptionToList assertEquals("GooglePayActivity was not found in the Android " + "manifest, or did not have a theme of R.style.bt_transparent_activity", exception.getMessage()); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_REQUEST_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_REQUEST_FAILED), any()); } // endregion @@ -1133,8 +1068,8 @@ public void tokenize_whenPaymentDataExists_returnsResultToListener_andSendsAnaly PaymentMethodNonce nonce = ((GooglePayResult.Success) result).getNonce(); PaymentMethodNonce expectedNonce = GooglePayCardNonce.fromJSON(new JSONObject(paymentDataJson)); assertEquals(nonce.getString(), expectedNonce.getString()); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_STARTED); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_SUCCEEDED); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.TOKENIZE_STARTED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.TOKENIZE_SUCCEEDED), any()); } @Test @@ -1156,8 +1091,8 @@ public void tokenize_whenErrorExists_returnsErrorToListener_andSendsAnalytics() GooglePayResult result = captor.getValue(); assertTrue(result instanceof GooglePayResult.Failure); assertEquals(error, ((GooglePayResult.Failure) result).getError()); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_STARTED); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.TOKENIZE_STARTED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.TOKENIZE_FAILED), any()); } @Test @@ -1179,8 +1114,8 @@ public void tokenize_whenUserCanceledErrorExists_returnsErrorToListener_andSends GooglePayResult result = captor.getValue(); assertTrue(result instanceof GooglePayResult.Cancel); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.TOKENIZE_STARTED); - verify(braintreeClient).sendAnalyticsEvent(GooglePayAnalytics.PAYMENT_SHEET_CANCELED); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.TOKENIZE_STARTED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(GooglePayAnalytics.PAYMENT_SHEET_CANCELED), any()); } // endregion diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.java b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.java index 1bc0693735..9761a627cd 100644 --- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.java +++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayLauncherUnitTest.java @@ -1,7 +1,9 @@ package com.braintreepayments.api.googlepay; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -10,15 +12,11 @@ import androidx.activity.result.contract.ActivityResultContract; import androidx.fragment.app.FragmentActivity; -import com.braintreepayments.api.googlepay.GooglePayLauncher; -import com.braintreepayments.api.googlepay.GooglePayLauncherCallback; -import com.braintreepayments.api.googlepay.GooglePayPaymentAuthRequestParams; -import com.braintreepayments.api.googlepay.GooglePayPaymentAuthResult; -import com.braintreepayments.api.googlepay.GooglePayRequest; import com.google.android.gms.wallet.PaymentDataRequest; import com.google.android.gms.wallet.TransactionInfo; import com.google.android.gms.wallet.WalletConstants; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,6 +28,8 @@ @RunWith(RobolectricTestRunner.class) public class GooglePayLauncherUnitTest { + private AutoCloseable closeable; + @Mock ActivityResultLauncher activityLauncher; @@ -37,18 +37,23 @@ public class GooglePayLauncherUnitTest { @Before public void beforeEach() { - MockitoAnnotations.initMocks(this); + closeable = MockitoAnnotations.openMocks(this); callback = mock(GooglePayLauncherCallback.class); } + @After + public void teardown() throws Exception { + closeable.close(); + } + @Test public void constructor_createsActivityLauncher() { String expectedKey = "com.braintreepayments.api.GooglePay.RESULT"; ActivityResultRegistry activityResultRegistry = mock(ActivityResultRegistry.class); FragmentActivity lifecycleOwner = new FragmentActivity(); - GooglePayLauncher sut = new GooglePayLauncher(activityResultRegistry, lifecycleOwner, - callback); + doReturn(activityLauncher).when(activityResultRegistry).register(any(), any(), any(), any()); + new GooglePayLauncher(activityResultRegistry, lifecycleOwner, callback); verify(activityResultRegistry).register(eq(expectedKey), same(lifecycleOwner), Mockito.>any(), @@ -71,9 +76,8 @@ public void launch_launchesActivity() { ActivityResultRegistry activityResultRegistry = mock(ActivityResultRegistry.class); FragmentActivity lifecycleOwner = new FragmentActivity(); - GooglePayLauncher sut = new GooglePayLauncher(activityResultRegistry, lifecycleOwner, - callback); - sut.activityLauncher = activityLauncher; + doReturn(activityLauncher).when(activityResultRegistry).register(any(), any(), any(), any()); + GooglePayLauncher sut = new GooglePayLauncher(activityResultRegistry, lifecycleOwner, callback); sut.launch(intentData); verify(activityLauncher).launch(intentData); diff --git a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayRequestUnitTest.java b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayRequestUnitTest.java index d642358e9f..b33391bf31 100644 --- a/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayRequestUnitTest.java +++ b/GooglePay/src/test/java/com/braintreepayments/api/googlepay/GooglePayRequestUnitTest.java @@ -70,7 +70,7 @@ public void constructor_setsDefaultValues() { assertFalse(request.isEmailRequired()); assertFalse(request.isPhoneNumberRequired()); assertFalse(request.isShippingAddressRequired()); - assertTrue(request.isCreditCardsAllowed()); + assertTrue(request.getAllowCreditCards()); assertNull(request.getShippingAddressRequirements()); assertNull(request.getTransactionInfo()); assertNull(request.getEnvironment());