Skip to content

Commit

Permalink
Google pay single result (#842)
Browse files Browse the repository at this point in the history
* Add GooglePayResult

* Implement GooglePayResult

* Fix unit tests for tokenize

* Add GooglePayIsReadyToPayResult

* Implement ready to pay result

* Fix demo

* Rename payment auth request

* Add GooglePayPaymentAuthRequest

* Implement GooglePayPaymentAuthRequest result

* Fix unit tests

* Fix demo app

* Fix demo integration

* Update CHANGELOG and migration guide

* Add ready to pay to migration guide

* Update GooglePay/src/main/java/com/braintreepayments/api/GooglePayClient.java

Co-authored-by: sshropshire <[email protected]>

* Convert error to cause

* Revert error to cause changes

* Update GooglePay/src/main/java/com/braintreepayments/api/GooglePayClient.java

Co-authored-by: Holly Richko <[email protected]>

* Update GooglePay/src/main/java/com/braintreepayments/api/GooglePayClient.java

Co-authored-by: Holly Richko <[email protected]>

* Fix spacing

* Update v5_MIGRATION_GUIDE.md

Co-authored-by: sshropshire <[email protected]>

* Update v5_MIGRATION_GUIDE.md

Co-authored-by: sshropshire <[email protected]>

---------

Co-authored-by: sshropshire <[email protected]>
Co-authored-by: Holly Richko <[email protected]>
  • Loading branch information
3 people authored Dec 18, 2023
1 parent 5633f12 commit 54f7f5d
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 157 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
* Remove overload constructors, `setListener, and `onActivityResult` from `GooglePayClient`
* Change `GooglePayClient#requestPayment` parameters and rename to
`GooglePayClient#createPaymentAuthRequest`
* Change `GooglePayIsReadyToPayCallback` parameters
* Add `GooglePayClient#tokenize`
* Remove `merchantId` from `GooglePayRequest`
* Change `GooglePayGetTokenizationParametersCallback` parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@

import com.braintreepayments.api.GooglePayClient;
import com.braintreepayments.api.GooglePayLauncher;
import com.braintreepayments.api.GooglePayPaymentAuthRequest;
import com.braintreepayments.api.GooglePayReadinessResult;
import com.braintreepayments.api.GooglePayRequest;
import com.braintreepayments.api.GooglePayResult;
import com.braintreepayments.api.PaymentMethodNonce;
import com.braintreepayments.api.UserCanceledException;
import com.google.android.gms.wallet.ShippingAddressRequirements;
import com.google.android.gms.wallet.TransactionInfo;
import com.google.android.gms.wallet.WalletConstants;
Expand All @@ -38,11 +42,13 @@ public View onCreateView(@NonNull LayoutInflater inflater,
googlePayClient = new GooglePayClient(requireContext(), super.getAuthStringArg());
googlePayLauncher = new GooglePayLauncher(this,
paymentAuthResult -> googlePayClient.tokenize(paymentAuthResult,
(paymentMethodNonce, error) -> {
if (error != null) {
handleError(error);
} else {
handleGooglePayActivityResult(paymentMethodNonce);
(googlePayResult) -> {
if (googlePayResult instanceof GooglePayResult.Failure) {
handleError(((GooglePayResult.Failure) googlePayResult).getError());
} else if (googlePayResult instanceof GooglePayResult.Success){
handleGooglePayActivityResult(((GooglePayResult.Success) googlePayResult).getNonce());
} else if (googlePayResult instanceof GooglePayResult.Cancel) {
handleError(new UserCanceledException("User canceled Google Pay"));
}
}));

Expand All @@ -53,8 +59,8 @@ public View onCreateView(@NonNull LayoutInflater inflater,
public void onResume() {
super.onResume();

googlePayClient.isReadyToPay(requireActivity(), (isReadyToPay, e) -> {
if (isReadyToPay) {
googlePayClient.isReadyToPay(requireActivity(), (googlePayReadinessResult) -> {
if (googlePayReadinessResult instanceof GooglePayReadinessResult.ReadyToPay) {
googlePayButton.setVisibility(View.VISIBLE);
} else {
showDialog(
Expand Down Expand Up @@ -99,8 +105,14 @@ public void launchGooglePay(View v) {
.addAllowedCountryCodes(Settings.getGooglePayAllowedCountriesForShipping(activity))
.build());

googlePayClient.createPaymentAuthRequest(googlePayRequest,
(paymentAuthRequest, error) -> googlePayLauncher.launch(paymentAuthRequest));
googlePayClient.createPaymentAuthRequest(googlePayRequest, (paymentAuthRequest) -> {
if (paymentAuthRequest instanceof GooglePayPaymentAuthRequest.ReadyToLaunch) {
googlePayLauncher.launch(
((GooglePayPaymentAuthRequest.ReadyToLaunch) paymentAuthRequest).getRequestParams());
} else if (paymentAuthRequest instanceof GooglePayPaymentAuthRequest.Failure) {
handleError(((GooglePayPaymentAuthRequest.Failure) paymentAuthRequest).getError());
}
});
}

private void handleGooglePayActivityResult(PaymentMethodNonce paymentMethodNonce) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import com.google.android.gms.wallet.AutoResolveHelper;
import com.google.android.gms.wallet.PaymentData;

class GooglePayActivityResultContract extends ActivityResultContract<GooglePayPaymentAuthRequest, GooglePayPaymentAuthResult> {
class GooglePayActivityResultContract extends ActivityResultContract<GooglePayPaymentAuthRequestParams, GooglePayPaymentAuthResult> {

@NonNull
@Override
public Intent createIntent(@NonNull Context context, GooglePayPaymentAuthRequest input) {
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,25 +97,24 @@ public void isReadyToPay(@NonNull final FragmentActivity activity,
try {
Class.forName(PaymentsClient.class.getName());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
callback.onResult(false, null);
callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(null));
return;
}

braintreeClient.getConfiguration((configuration, e) -> {
if (configuration == null) {
callback.onResult(false, e);
callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(e));
return;
}

if (!configuration.isGooglePayEnabled()) {
callback.onResult(false, null);
callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(null));
return;
}

//noinspection ConstantConditions
if (activity == null) {
callback.onResult(false,
new IllegalArgumentException("Activity cannot be null."));
callback.onGooglePayReadinessResult(new GooglePayReadinessResult.NotReadyToPay(new IllegalArgumentException("Activity cannot be null.")));
return;
}

Expand Down Expand Up @@ -169,9 +168,9 @@ public void getTokenizationParameters(
}

/**
* Start the Google Pay payment flow. This will return a {@link GooglePayPaymentAuthRequest} that will
* be used to present Google Pay payment sheet in
* {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequest)}
* 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}
Expand All @@ -181,39 +180,37 @@ public void createPaymentAuthRequest(@NonNull final GooglePayRequest request,
braintreeClient.sendAnalyticsEvent("google-payment.selected");

if (!validateManifest()) {
callback.onResult(null, new BraintreeException(
"GooglePayActivity was " + "not found in the " + "Android " +
"manifest, or did not have a theme of R.style.bt_transparent_activity"));
callback.onGooglePayPaymentAuthRequest(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")));
braintreeClient.sendAnalyticsEvent("google-payment.failed");
return;
}

//noinspection ConstantConditions
if (request == null) {
callback.onResult(null, new BraintreeException(
"Cannot pass null " + "GooglePayRequest to " + "requestPayment"));
callback.onGooglePayPaymentAuthRequest(new GooglePayPaymentAuthRequest.Failure(new BraintreeException(
"Cannot pass null GooglePayRequest to requestPayment")));
braintreeClient.sendAnalyticsEvent("google-payment.failed");
return;
}

if (request.getTransactionInfo() == null) {
callback.onResult(null, new BraintreeException(
"Cannot pass null " + "TransactionInfo to" + " requestPayment"));
callback.onGooglePayPaymentAuthRequest(new GooglePayPaymentAuthRequest.Failure(new BraintreeException(
"Cannot pass null TransactionInfo to requestPayment")));
braintreeClient.sendAnalyticsEvent("google-payment.failed");
return;
}

braintreeClient.getConfiguration((configuration, configError) -> {
if (configuration == null) {
callback.onResult(null, configError);
if (configuration == null && configError != null) {
callback.onGooglePayPaymentAuthRequest(new GooglePayPaymentAuthRequest.Failure(configError));
return;
}

if (!configuration.isGooglePayEnabled()) {
callback.onResult(null, new BraintreeException(
"Google Pay " +
"is not enabled for your Braintree account," +
" or Google Play Services are not configured correctly."));
callback.onGooglePayPaymentAuthRequest(new GooglePayPaymentAuthRequest.Failure(new BraintreeException(
"Google Pay is not enabled for your Braintree account, or Google Play Services are not configured correctly.")));
return;
}

Expand All @@ -223,10 +220,10 @@ public void createPaymentAuthRequest(@NonNull final GooglePayRequest request,
PaymentDataRequest paymentDataRequest =
PaymentDataRequest.fromJson(request.toJson());

GooglePayPaymentAuthRequest intent =
new GooglePayPaymentAuthRequest(getGooglePayEnvironment(configuration),
GooglePayPaymentAuthRequestParams params =
new GooglePayPaymentAuthRequestParams(getGooglePayEnvironment(configuration),
paymentDataRequest);
callback.onResult(intent, null);
callback.onGooglePayPaymentAuthRequest(new GooglePayPaymentAuthRequest.ReadyToLaunch(params));

});

Expand All @@ -235,7 +232,7 @@ public void createPaymentAuthRequest(@NonNull final GooglePayRequest request,
void tokenize(PaymentData paymentData, GooglePayTokenizeCallback callback) {
try {
JSONObject result = new JSONObject(paymentData.toJson());
callback.onResult(GooglePayCardNonce.fromJSON(result), null);
callback.onGooglePayResult(new GooglePayResult.Success(GooglePayCardNonce.fromJSON(result)));
braintreeClient.sendAnalyticsEvent("google-payment.nonce-received");
} catch (JSONException | NullPointerException e) {
braintreeClient.sendAnalyticsEvent("google-payment.failed");
Expand All @@ -244,9 +241,9 @@ void tokenize(PaymentData paymentData, GooglePayTokenizeCallback callback) {
String token =
new JSONObject(paymentData.toJson()).getJSONObject("paymentMethodData")
.getJSONObject("tokenizationData").getString("token");
callback.onResult(null, ErrorWithResponse.fromJson(token));
callback.onGooglePayResult(new GooglePayResult.Failure(ErrorWithResponse.fromJson(token)));
} catch (JSONException | NullPointerException e1) {
callback.onResult(null, e1);
callback.onGooglePayResult(new GooglePayResult.Failure(e1));
}
}
}
Expand All @@ -258,7 +255,7 @@ void tokenize(PaymentData paymentData, GooglePayTokenizeCallback callback) {
* method should be invoked to tokenize the payment method to retrieve a
* {@link PaymentMethodNonce}
*
* @param paymentAuthResult the result of {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequest)}
* @param paymentAuthResult the result of {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequestParams)}
* @param callback {@link GooglePayTokenizeCallback}
*/
public void tokenize(GooglePayPaymentAuthResult paymentAuthResult,
Expand All @@ -269,10 +266,11 @@ public void tokenize(GooglePayPaymentAuthResult paymentAuthResult,
} else if (paymentAuthResult.getError() != null) {
if (paymentAuthResult.getError() instanceof UserCanceledException) {
braintreeClient.sendAnalyticsEvent("google-payment.canceled");
} else {
braintreeClient.sendAnalyticsEvent("google-payment.failed");
callback.onGooglePayResult(GooglePayResult.Cancel.INSTANCE);
return;
}
callback.onResult(null, paymentAuthResult.getError());
braintreeClient.sendAnalyticsEvent("google-payment.failed");
callback.onGooglePayResult(new GooglePayResult.Failure(paymentAuthResult.getError()));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.braintreepayments.api;

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.wallet.IsReadyToPayRequest;
import com.google.android.gms.wallet.PaymentsClient;
import com.google.android.gms.wallet.Wallet;
Expand All @@ -18,14 +15,16 @@ void isReadyToPay(FragmentActivity activity, Configuration configuration, IsRead
new Wallet.WalletOptions.Builder()
.setEnvironment(getGooglePayEnvironment(configuration))
.build());
paymentsClient.isReadyToPay(isReadyToPayRequest).addOnCompleteListener(new OnCompleteListener<Boolean>() {
@Override
public void onComplete(@NonNull Task<Boolean> task) {
try {
callback.onResult(task.getResult(ApiException.class), null);
} catch (ApiException e) {
callback.onResult(false, e);
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));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.braintreepayments.api;

import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;

/**
Expand All @@ -11,8 +10,7 @@
public interface GooglePayIsReadyToPayCallback {

/**
* @param isReadyToPay true if Google Pay is ready; false otherwise.
* @param error an exception that occurred while checking if Google Pay is ready
* @param googlePayReadinessResult
*/
void onResult(boolean isReadyToPay, @Nullable Exception error);
void onGooglePayReadinessResult(GooglePayReadinessResult googlePayReadinessResult);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class GooglePayLauncher {

@VisibleForTesting
ActivityResultLauncher<GooglePayPaymentAuthRequest> activityLauncher;
ActivityResultLauncher<GooglePayPaymentAuthRequestParams> activityLauncher;

private static final String GOOGLE_PAY_RESULT = "com.braintreepayments.api.GooglePay.RESULT";

Expand All @@ -36,7 +36,7 @@ public GooglePayLauncher(@NonNull FragmentActivity activity,
new GooglePayActivityResultContract(), callback::onResult);
}

public void launch(GooglePayPaymentAuthRequest googlePayPaymentAuthRequest) {
activityLauncher.launch(googlePayPaymentAuthRequest);
public void launch(GooglePayPaymentAuthRequestParams googlePayPaymentAuthRequestParams) {
activityLauncher.launch(googlePayPaymentAuthRequestParams);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.braintreepayments.api

/**
* A request used to launch the Venmo app for continuation of the Google Pay flow.
*/
sealed class GooglePayPaymentAuthRequest {

/**
* The request was successfully created and is ready to be launched by [GooglePayLauncher]
*/
class ReadyToLaunch(val requestParams: GooglePayPaymentAuthRequestParams) : GooglePayPaymentAuthRequest()

/**
* There was an [error] creating the request
*/
class Failure(val error: Exception) : GooglePayPaymentAuthRequest()
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.braintreepayments.api;

import androidx.annotation.Nullable;

/**
* Callback to handle result from
* {@link GooglePayClient#createPaymentAuthRequest(GooglePayRequest, GooglePayPaymentAuthRequestCallback)}
*/
public interface GooglePayPaymentAuthRequestCallback {

void onResult(@Nullable GooglePayPaymentAuthRequest paymentAuthRequest,
@Nullable Exception error);
void onGooglePayPaymentAuthRequest(GooglePayPaymentAuthRequest paymentAuthRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

/**
* Used to request Google Pay payment authorization via
* {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequest)}
* {@link GooglePayLauncher#launch(GooglePayPaymentAuthRequestParams)}
*/
public class GooglePayPaymentAuthRequest {
public class GooglePayPaymentAuthRequestParams {

private final int googlePayEnvironment;
private final PaymentDataRequest paymentDataRequest;

GooglePayPaymentAuthRequest(int googlePayEnvironment, @NonNull PaymentDataRequest paymentDataRequest) {
GooglePayPaymentAuthRequestParams(int googlePayEnvironment, @NonNull PaymentDataRequest paymentDataRequest) {
this.googlePayEnvironment = googlePayEnvironment;
this.paymentDataRequest = paymentDataRequest;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.braintreepayments.api

/**
* Result of [GooglePayClient.isReadyToPay]
*/
sealed class GooglePayReadinessResult {

/**
* The Google Pay API is supported and set up on this device. Show the Google Pay button for
* Google Pay.
*/
object ReadyToPay : GooglePayReadinessResult()

/**
* The Google Pay API is supported or not set up on this device, or there was an issue [error]
* determining readiness.
*/
class NotReadyToPay(val error: Exception?) : GooglePayReadinessResult()
}
Loading

0 comments on commit 54f7f5d

Please sign in to comment.