Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Venmo - Support App Link Returns #1190

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,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 @@ -157,18 +172,18 @@ class VenmoClient internal constructor(
context.packageManager.getApplicationLabel(context.applicationInfo)
.toString()

val returnUrlScheme = braintreeClient.getReturnUrlScheme()
val merchantBaseUri = braintreeClient.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 @@ -184,7 +199,8 @@ class VenmoClient internal constructor(
val browserSwitchOptions = BrowserSwitchOptions()
.requestCode(BraintreeRequestCodes.VENMO.code)
.url(venmoBaseURL)
.returnUrlScheme(returnUrlScheme)
.appLinkUri(braintreeClient.appLinkReturnUri)
.returnUrlScheme(braintreeClient.getReturnUrlScheme())
val params = VenmoPaymentAuthRequestParams(
browserSwitchOptions
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,39 @@ 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)
.integration(IntegrationType.CUSTOM)
.authorizationSuccess(clientToken)
.appLinkReturnUri(Uri.parse("https://example.com/payments"))
.build();

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

VenmoRequest request = new VenmoRequest(VenmoPaymentMethodUsage.SINGLE_USE);
request.setProfileId(null);
request.setShouldVault(false);
VenmoClient sut =
new VenmoClient(braintreeClient, apiClient, venmoApi, sharedPrefsWriter, analyticsParamRepository);
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
Loading