Skip to content

Commit

Permalink
Venmo - Add deep link fallback (#1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdchow authored Dec 10, 2024
1 parent caf65bd commit 7fc4f26
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 82 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Add `ThreeDSecureRequest.requestorAppUrl`
* Venmo
* Add `VenmoClient` constructor with `appLinkReturnUri` argument to use App Links when redirecting back from the Venmo flow
* Add `deepLinkFallbackUrlScheme` to `VenmoClient` constructor params for supporting deep link fallback
* Deprecate `VenmoClient` constructor with `returnUrlScheme` argument

## 5.2.0 (2024-10-30)
Expand Down
1 change: 1 addition & 0 deletions Demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

<data android:scheme="https" />
<data android:host="mobile-sdk-demo-site-838cead5d3ab.herokuapp.com" />
<data android:pathPrefix="/braintree-payments" />
</intent-filter>
</activity>
</application>
Expand Down
34 changes: 18 additions & 16 deletions Demo/src/main/java/com/braintreepayments/demo/VenmoFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;

import com.braintreepayments.api.core.UserCanceledException;
import com.braintreepayments.api.venmo.VenmoAccountNonce;
import com.braintreepayments.api.venmo.VenmoClient;
import com.braintreepayments.api.venmo.VenmoLauncher;
Expand All @@ -25,7 +26,6 @@
import com.braintreepayments.api.venmo.VenmoPendingRequest;
import com.braintreepayments.api.venmo.VenmoRequest;
import com.braintreepayments.api.venmo.VenmoResult;
import com.braintreepayments.api.core.UserCanceledException;

import java.util.ArrayList;

Expand All @@ -42,6 +42,18 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
venmoButton = view.findViewById(R.id.venmo_button);
venmoButton.setOnClickListener(this::launchVenmo);

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

return view;
Expand Down Expand Up @@ -71,35 +83,24 @@ private void handleVenmoResult(VenmoResult result) {
handleError(new UserCanceledException("User canceled Venmo"));
}
}

private void handleVenmoAccountNonce(VenmoAccountNonce venmoAccountNonce) {
super.onPaymentMethodNonceCreated(venmoAccountNonce);

NavDirections action =
VenmoFragmentDirections.actionVenmoFragmentToDisplayNonceFragment(venmoAccountNonce);
NavDirections action = VenmoFragmentDirections.actionVenmoFragmentToDisplayNonceFragment(venmoAccountNonce);
NavHostFragment.findNavController(this).navigate(action);
}

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

getActivity().setProgressBarIndeterminateVisibility(true);
if (venmoClient == 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());
}
}

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

VenmoPaymentMethodUsage venmoPaymentMethodUsage = shouldVault ?
VenmoPaymentMethodUsage.MULTI_USE : VenmoPaymentMethodUsage.SINGLE_USE;
VenmoPaymentMethodUsage.MULTI_USE : VenmoPaymentMethodUsage.SINGLE_USE;
VenmoRequest venmoRequest = new VenmoRequest(venmoPaymentMethodUsage);
venmoRequest.setProfileId(null);
venmoRequest.setShouldVault(shouldVault);
Expand Down Expand Up @@ -139,6 +140,7 @@ private void completeVenmoFlow(VenmoPaymentAuthResult.Success paymentAuthResult)
private void storePendingRequest(VenmoPendingRequest.Started request) {
PendingRequestStore.getInstance().putVenmoPendingRequest(requireContext(), request);
}

private VenmoPendingRequest.Started getPendingRequest() {
return PendingRequestStore.getInstance().getVenmoPendingRequest(requireContext());
}
Expand Down
75 changes: 54 additions & 21 deletions Venmo/src/main/java/com/braintreepayments/api/venmo/VenmoClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.braintreepayments.api.core.BraintreeException
import com.braintreepayments.api.core.BraintreeRequestCodes
import com.braintreepayments.api.core.ClientToken
import com.braintreepayments.api.core.Configuration
import com.braintreepayments.api.core.GetReturnLinkUseCase
import com.braintreepayments.api.core.MerchantRepository
import com.braintreepayments.api.core.MetadataBuilder
import org.json.JSONException
Expand All @@ -32,7 +33,8 @@ class VenmoClient internal constructor(
private val sharedPrefsWriter: VenmoSharedPrefsWriter = VenmoSharedPrefsWriter(),
private val analyticsParamRepository: AnalyticsParamRepository = AnalyticsParamRepository.instance,
private val merchantRepository: MerchantRepository = MerchantRepository.instance,
private val venmoRepository: VenmoRepository = VenmoRepository.instance
private val venmoRepository: VenmoRepository = VenmoRepository.instance,
private val getReturnLinkUseCase: GetReturnLinkUseCase = GetReturnLinkUseCase(merchantRepository)
) {
/**
* Used for linking events from the client to server side request
Expand All @@ -52,12 +54,24 @@ class VenmoClient internal constructor(
* @param authorization a Tokenization Key or Client Token used to authenticate
* @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
* @param deepLinkFallbackUrlScheme A return url scheme that will be used as a deep link fallback when returning to
* your app via App Link is not available (buyer unchecks the "Open supported links" setting).
*/
@JvmOverloads
constructor(
context: Context,
authorization: String,
appLinkReturnUrl: Uri,
) : this(BraintreeClient(context, authorization, null, appLinkReturnUrl))
deepLinkFallbackUrlScheme: String? = null
) : this(
BraintreeClient(
context = context,
authorization = authorization,
returnUrlScheme = null,
appLinkReturnUri = appLinkReturnUrl,
deepLinkFallbackUrlScheme = deepLinkFallbackUrlScheme
)
)

/**
* Initializes a new [VenmoClient] instance
Expand All @@ -67,7 +81,8 @@ class VenmoClient internal constructor(
* @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(
@JvmOverloads
constructor(
context: Context,
authorization: String,
returnUrlScheme: String? = null
Expand All @@ -82,6 +97,7 @@ class VenmoClient internal constructor(
* @param request [VenmoRequest]
* @param callback [VenmoPaymentAuthRequestCallback]
*/
@Suppress("LongMethod", "CyclomaticComplexMethod", "TooGenericExceptionCaught")
fun createPaymentAuthRequest(
context: Context,
request: VenmoRequest,
Expand Down Expand Up @@ -138,8 +154,14 @@ class VenmoClient internal constructor(
merchantRepository.authorization, finalVenmoProfileId,
paymentContextId, callback
)
} catch (e: JSONException) {
callbackPaymentAuthFailure(callback, VenmoPaymentAuthRequest.Failure(e))
} catch (e: Exception) {
when (e) {
is JSONException, is BraintreeException -> {
callbackPaymentAuthFailure(callback, VenmoPaymentAuthRequest.Failure(e))
}

else -> throw e
}
}
} else {
callbackPaymentAuthFailure(callback, VenmoPaymentAuthRequest.Failure(exception))
Expand Down Expand Up @@ -171,22 +193,27 @@ class VenmoClient internal constructor(
val braintreeData = JSONObject()
.put("_meta", metadata)

val applicationName =
context.packageManager.getApplicationLabel(context.applicationInfo)
.toString()
val applicationName = context.packageManager.getApplicationLabel(context.applicationInfo).toString()

val merchantBaseUri = merchantRepository.appLinkReturnUri
?: Uri.parse("${braintreeClient.getReturnUrlScheme()}://x-callback-url/vzero/auth/venmo")
val returnLinkResult = getReturnLinkUseCase()
val merchantBaseUrl: String = when (returnLinkResult) {
is GetReturnLinkUseCase.ReturnLinkResult.AppLink -> returnLinkResult.appLinkReturnUri.toString()
is GetReturnLinkUseCase.ReturnLinkResult.DeepLink -> {
"${returnLinkResult.deepLinkFallbackUrlScheme}://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()
is GetReturnLinkUseCase.ReturnLinkResult.Failure -> throw returnLinkResult.exception
}

val successUri = "$merchantBaseUrl/success"
val cancelUri = "$merchantBaseUrl/cancel"
val errorUri = "$merchantBaseUrl/error"

val venmoBaseURL = Uri.parse("https://venmo.com/go/checkout")
.buildUpon()
.appendQueryParameter("x-success", successUri.toString())
.appendQueryParameter("x-error", errorUri.toString())
.appendQueryParameter("x-cancel", cancelUri.toString())
.appendQueryParameter("x-success", successUri)
.appendQueryParameter("x-error", errorUri)
.appendQueryParameter("x-cancel", cancelUri)
.appendQueryParameter("x-source", applicationName)
.appendQueryParameter("braintree_merchant_id", venmoProfileId)
.appendQueryParameter("braintree_access_token", configuration?.venmoAccessToken)
Expand All @@ -204,11 +231,17 @@ class VenmoClient internal constructor(
val browserSwitchOptions = BrowserSwitchOptions()
.requestCode(BraintreeRequestCodes.VENMO.code)
.url(venmoBaseURL)
.appLinkUri(merchantRepository.appLinkReturnUri)
.returnUrlScheme(braintreeClient.getReturnUrlScheme())
val params = VenmoPaymentAuthRequestParams(
browserSwitchOptions
)
.apply {
when (returnLinkResult) {
is GetReturnLinkUseCase.ReturnLinkResult.AppLink -> appLinkUri(returnLinkResult.appLinkReturnUri)
is GetReturnLinkUseCase.ReturnLinkResult.DeepLink -> {
returnUrlScheme(returnLinkResult.deepLinkFallbackUrlScheme)
}

is GetReturnLinkUseCase.ReturnLinkResult.Failure -> throw returnLinkResult.exception
}
}
val params = VenmoPaymentAuthRequestParams(browserSwitchOptions)

callback.onVenmoPaymentAuthRequest(VenmoPaymentAuthRequest.ReadyToLaunch(params))
}
Expand Down
Loading

0 comments on commit 7fc4f26

Please sign in to comment.