From 2dbc07c583af99a35f6f9dd98281ae7a032642f8 Mon Sep 17 00:00:00 2001 From: Sarah Koop Date: Mon, 11 Dec 2023 15:05:00 -0600 Subject: [PATCH] VenmoListener NPE Fix (#848) * Check for listener before returning result * Add unit test * Add CHANGELOG * Fix spacing * Log warning if listener is null * Move logging to utils class * Update tag name * Fix lint --- CHANGELOG.md | 5 +++ .../com/braintreepayments/api/LoggingUtils.kt | 14 +++++++ .../braintreepayments/api/VenmoClient.java | 37 ++++++++++++++----- .../api/VenmoClientUnitTest.java | 25 +++++++++++++ 4 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 SharedUtils/src/main/java/com/braintreepayments/api/LoggingUtils.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 60279ba6cf..26f656aa9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Braintree Android SDK Release Notes +## unreleased + +* Venmo + * Fix NPE when `VenmoListener` is null (fixes #832) + ## 4.40.0 (2023-11-16) * PayPalNativeCheckout diff --git a/SharedUtils/src/main/java/com/braintreepayments/api/LoggingUtils.kt b/SharedUtils/src/main/java/com/braintreepayments/api/LoggingUtils.kt new file mode 100644 index 0000000000..7e07e7a605 --- /dev/null +++ b/SharedUtils/src/main/java/com/braintreepayments/api/LoggingUtils.kt @@ -0,0 +1,14 @@ +package com.braintreepayments.api + +import androidx.annotation.RestrictTo + +/** + * @suppress + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +object LoggingUtils { + + const val TAG = "Braintree SDK" + + const val LISTENER_WARNING = "Unable to deliver result to null listener" +} diff --git a/Venmo/src/main/java/com/braintreepayments/api/VenmoClient.java b/Venmo/src/main/java/com/braintreepayments/api/VenmoClient.java index 387bacd081..2b484dd8bc 100644 --- a/Venmo/src/main/java/com/braintreepayments/api/VenmoClient.java +++ b/Venmo/src/main/java/com/braintreepayments/api/VenmoClient.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.net.Uri; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -132,7 +133,7 @@ public void tokenizeVenmoAccount(@NonNull final FragmentActivity activity, @NonN @Override public void onResult(@Nullable Exception error) { if (error != null) { - listener.onVenmoFailure(error); + deliverVenmoFailure(error); } } }); @@ -254,19 +255,19 @@ public void onResult(@Nullable VenmoAccountNonce nonce, @Nullable Exception erro @Override public void onResult(@Nullable VenmoAccountNonce venmoAccountNonce, @Nullable Exception error) { if (venmoAccountNonce != null) { - listener.onVenmoSuccess(venmoAccountNonce); + deliverVenmoSuccess(venmoAccountNonce); } else if (error != null) { - listener.onVenmoFailure(error); + deliverVenmoFailure(error); } } }); } else { braintreeClient.sendAnalyticsEvent("pay-with-venmo.app-switch.failure"); - listener.onVenmoSuccess(nonce); + deliverVenmoSuccess(nonce); } } else { braintreeClient.sendAnalyticsEvent("pay-with-venmo.app-switch.failure"); - listener.onVenmoFailure(error); + deliverVenmoFailure(error); } } }); @@ -279,21 +280,21 @@ public void onResult(@Nullable VenmoAccountNonce venmoAccountNonce, @Nullable Ex @Override public void onResult(@Nullable VenmoAccountNonce venmoAccountNonce, @Nullable Exception error) { if (venmoAccountNonce != null) { - listener.onVenmoSuccess(venmoAccountNonce); + deliverVenmoSuccess(venmoAccountNonce); } else if (error != null) { - listener.onVenmoFailure(error); + deliverVenmoFailure(error); } } }); } else { String venmoUsername = venmoResult.getVenmoUsername(); VenmoAccountNonce venmoAccountNonce = new VenmoAccountNonce(nonce, venmoUsername, false); - listener.onVenmoSuccess(venmoAccountNonce); + deliverVenmoSuccess(venmoAccountNonce); } } } else if (authError != null) { - listener.onVenmoFailure(authError); + deliverVenmoFailure(authError); } } }); @@ -302,7 +303,23 @@ public void onResult(@Nullable VenmoAccountNonce venmoAccountNonce, @Nullable Ex if (venmoResult.getError() instanceof UserCanceledException) { braintreeClient.sendAnalyticsEvent("pay-with-venmo.app-switch.canceled"); } - listener.onVenmoFailure(venmoResult.getError()); + deliverVenmoFailure(venmoResult.getError()); + } + } + + private void deliverVenmoSuccess(VenmoAccountNonce venmoAccountNonce) { + if (listener != null) { + listener.onVenmoSuccess(venmoAccountNonce); + } else { + Log.w(LoggingUtils.TAG, LoggingUtils.LISTENER_WARNING); + } + } + + private void deliverVenmoFailure(Exception error) { + if (listener != null) { + listener.onVenmoFailure(error); + } else { + Log.w(LoggingUtils.TAG, LoggingUtils.LISTENER_WARNING); } } diff --git a/Venmo/src/test/java/com/braintreepayments/api/VenmoClientUnitTest.java b/Venmo/src/test/java/com/braintreepayments/api/VenmoClientUnitTest.java index ce883ae498..be54ca7f3b 100644 --- a/Venmo/src/test/java/com/braintreepayments/api/VenmoClientUnitTest.java +++ b/Venmo/src/test/java/com/braintreepayments/api/VenmoClientUnitTest.java @@ -156,6 +156,31 @@ public void showVenmoInGooglePlayStore_sendsAnalyticsEvent() { verify(braintreeClient).sendAnalyticsEvent("android.pay-with-venmo.app-store.invoked"); } + @Test + public void tokenizeVenmoAccount_whenListenerNull_returnsNothing() { + BraintreeClient braintreeClient = new MockBraintreeClientBuilder() + .configuration(venmoEnabledConfiguration) + .sessionId("session-id") + .integration("custom") + .authorizationSuccess(clientToken) + .build(); + + VenmoApi venmoApi = new MockVenmoApiBuilder() + .createPaymentContextSuccess("venmo-payment-context-id") + .build(); + + when(deviceInspector.isVenmoAppSwitchAvailable(activity)).thenReturn(true); + + VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE); + request.setProfileId("sample-venmo-merchant"); + request.setCollectCustomerBillingAddress(true); + + VenmoClient sut = new VenmoClient(null, null, braintreeClient, venmoApi, sharedPrefsWriter, deviceInspector); + sut.tokenizeVenmoAccount(activity, request); + verify(listener, never()).onVenmoFailure(any(Exception.class)); + verify(listener, never()).onVenmoSuccess(any(VenmoAccountNonce.class)); + } + @Test public void tokenizeVenmoAccount_whenCreatePaymentContextSucceeds_withObserver_launchesObserverWithVenmoIntentData_andSendsAnalytics() { BraintreeClient braintreeClient = new MockBraintreeClientBuilder()