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 - Add deep link fallback #1237

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /braintree-payments path was added to the app link URL at some point which broke the app link flow in the demo app.

</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
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
Loading