Skip to content

Commit

Permalink
Venmo - Support App Link Returns (#1190)
Browse files Browse the repository at this point in the history
  • Loading branch information
scannillo authored Dec 5, 2024
1 parent d0e0e6f commit 15db334
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 16 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
* Shopper Insights (BETA)
* For analytics, send `experiment` as a parameter to `getRecommendedPaymentMethods` method
* For analytics, send `experiment` and `paymentMethodsDisplayed` analytic metrics to FPTI via the button presented event methods
* Venmo
* Add `VenmoClient` constructor with `appLinkReturnUri` argument to use App Links when redirecting back from the Venmo flow
* Deprecate `VenmoClient` constructor with `returnUrlScheme` argument

## 5.1.0 (2024-10-15)

Expand Down
4 changes: 4 additions & 0 deletions Demo/src/main/java/com/braintreepayments/demo/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ public static boolean vaultVenmo(Context context) {
return getPreferences(context).getBoolean("vault_venmo", true);
}

public static boolean useAppLinkReturn(Context context) {
return getPreferences(context).getBoolean("use_app_link_return", true);
}

public static boolean isAmexRewardsBalanceEnabled(Context context) {
return getPreferences(context).getBoolean("amex_rewards_balance", false);
}
Expand Down
15 changes: 12 additions & 3 deletions Demo/src/main/java/com/braintreepayments/demo/VenmoFragment.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.braintreepayments.demo;

import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
Expand Down Expand Up @@ -79,13 +80,21 @@ private void handleVenmoAccountNonce(VenmoAccountNonce venmoAccountNonce) {
}

public void launchVenmo(View v) {
FragmentActivity activity = getActivity();

getActivity().setProgressBarIndeterminateVisibility(true);
if (venmoClient == null) {
venmoClient = new VenmoClient(requireContext(), super.getAuthStringArg(), null);
if (Settings.useAppLinkReturn(activity)) {
venmoClient = new VenmoClient(
requireContext(),
super.getAuthStringArg(),
Uri.parse("https://mobile-sdk-demo-site-838cead5d3ab.herokuapp.com/braintree-payments")
);
} else {
venmoClient = new VenmoClient(requireContext(), super.getAuthStringArg());
}
}

FragmentActivity activity = getActivity();

boolean shouldVault =
Settings.vaultVenmo(activity) && !TextUtils.isEmpty(Settings.getCustomerId(activity));

Expand Down
2 changes: 2 additions & 0 deletions Demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
<string name="venmo">Venmo</string>
<string name="vault_venmo">Vault Venmo</string>
<string name="vault_venmo_summary">Vault Venmo payment methods on creation. Requires a customer id to be set.</string>
<string name="use_app_links_return">Use App Link</string>
<string name="use_app_links_return_summary">Use merchant App Link, instead of deeplink, when redirecting back from Venmo flow.</string>
<string name="amex">Amex</string>
<string name="amex_rewards_balance">Get Rewards Balance</string>
<string name="amex_rewards_balance_summary">Fetch Amex Rewards Balance on Card Tokenization. Requires a Client Token and relevant configurations.</string>
Expand Down
6 changes: 6 additions & 0 deletions Demo/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@
android:summary="@string/vault_venmo_summary"
android:defaultValue="true" />

<CheckBoxPreference
android:key="use_app_links_return"
android:title="@string/use_app_links_return"
android:summary="@string/use_app_links_return_summary"
android:defaultValue="true" />

</PreferenceCategory>

<PreferenceCategory
Expand Down
42 changes: 29 additions & 13 deletions Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,27 @@ class VenmoClient internal constructor(
*
* @param context an Android Context
* @param authorization a Tokenization Key or Client Token used to authenticate
* @param returnUrlScheme a custom return url to use for browser and app switching
* @param appLinkReturnUrl A [Uri] containing the Android App Link website associated with
* your application to be used to return to your app from the PayPal
*/
constructor(
context: Context,
authorization: String,
returnUrlScheme: String?
appLinkReturnUrl: Uri,
) : this(BraintreeClient(context, authorization, null, appLinkReturnUrl))

/**
* Initializes a new [VenmoClient] instance
*
* @param context an Android Context
* @param authorization a Tokenization Key or Client Token used to authenticate
* @param returnUrlScheme a custom return url to use for browser and app switching
*/
@Deprecated("Use the constructor that uses an `appLinkReturnUrl` to redirect back to your application instead.")
@JvmOverloads constructor(
context: Context,
authorization: String,
returnUrlScheme: String? = null
) : this(BraintreeClient(context, authorization, returnUrlScheme))

/**
Expand Down Expand Up @@ -160,18 +175,18 @@ class VenmoClient internal constructor(
context.packageManager.getApplicationLabel(context.applicationInfo)
.toString()

val returnUrlScheme = braintreeClient.getReturnUrlScheme()
val merchantBaseUri = merchantRepository.appLinkReturnUri
?: Uri.parse("${braintreeClient.getReturnUrlScheme()}://x-callback-url/vzero/auth/venmo")

val successUri = merchantBaseUri.buildUpon().appendPath("success").build()
val cancelUri = merchantBaseUri.buildUpon().appendPath("cancel").build()
val errorUri = merchantBaseUri.buildUpon().appendPath("error").build()

val venmoBaseURL = Uri.parse("https://venmo.com/go/checkout")
.buildUpon()
.appendQueryParameter(
"x-success", "$returnUrlScheme://x-callback-url/vzero/auth/venmo/success"
)
.appendQueryParameter(
"x-error", "$returnUrlScheme://x-callback-url/vzero/auth/venmo/error"
)
.appendQueryParameter(
"x-cancel", "$returnUrlScheme://x-callback-url/vzero/auth/venmo/cancel"
)
.appendQueryParameter("x-success", successUri.toString())
.appendQueryParameter("x-error", errorUri.toString())
.appendQueryParameter("x-cancel", cancelUri.toString())
.appendQueryParameter("x-source", applicationName)
.appendQueryParameter("braintree_merchant_id", venmoProfileId)
.appendQueryParameter("braintree_access_token", configuration?.venmoAccessToken)
Expand All @@ -189,7 +204,8 @@ class VenmoClient internal constructor(
val browserSwitchOptions = BrowserSwitchOptions()
.requestCode(BraintreeRequestCodes.VENMO.code)
.url(venmoBaseURL)
.returnUrlScheme(returnUrlScheme)
.appLinkUri(merchantRepository.appLinkReturnUri)
.returnUrlScheme(braintreeClient.getReturnUrlScheme())
val params = VenmoPaymentAuthRequestParams(
browserSwitchOptions
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,46 @@ public void createPaymentAuthRequest_whenProfileIdIsNull_appSwitchesWithMerchant
assertEquals("merchant-id", url.getQueryParameter("braintree_merchant_id"));
}

@Test
public void createPaymentAuthRequest_whenAppLinkUriSet_appSwitchesWithAppLink() {
BraintreeClient braintreeClient = new MockBraintreeClientBuilder()
.configuration(venmoEnabledConfiguration)
.build();

VenmoApi venmoApi = new MockVenmoApiBuilder()
.createPaymentContextSuccess("venmo-payment-context-id")
.build();

VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE);
request.setProfileId(null);
request.setShouldVault(false);

when(merchantRepository.getAppLinkReturnUri()).thenReturn(Uri.parse("https://example.com/payments"));

VenmoClient sut = new VenmoClient(
braintreeClient,
apiClient,
venmoApi,
sharedPrefsWriter,
analyticsParamRepository,
merchantRepository,
venmoRepository
);
sut.createPaymentAuthRequest(context, request, venmoPaymentAuthRequestCallback);

ArgumentCaptor<VenmoPaymentAuthRequest> captor =
ArgumentCaptor.forClass(VenmoPaymentAuthRequest.class);
verify(venmoPaymentAuthRequestCallback).onVenmoPaymentAuthRequest(captor.capture());
VenmoPaymentAuthRequest paymentAuthRequest = captor.getValue();
VenmoPaymentAuthRequestParams params = ((VenmoPaymentAuthRequest.ReadyToLaunch) paymentAuthRequest).getRequestParams();
BrowserSwitchOptions browserSwitchOptions = params.getBrowserSwitchOptions();

Uri url = browserSwitchOptions.getUrl();
assertEquals("https://example.com/payments/success", url.getQueryParameter("x-success"));
assertEquals("https://example.com/payments/error", url.getQueryParameter("x-error"));
assertEquals("https://example.com/payments/cancel", url.getQueryParameter("x-cancel"));
}

@Test
public void createPaymentAuthRequest_whenProfileIdIsSpecified_appSwitchesWithProfileIdAndAccessToken() {
BraintreeClient braintreeClient = new MockBraintreeClientBuilder()
Expand Down

0 comments on commit 15db334

Please sign in to comment.