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()