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

Add VenmoLauncher API #798

Merged
merged 20 commits into from
Oct 10, 2023
Merged

Add VenmoLauncher API #798

merged 20 commits into from
Oct 10, 2023

Conversation

sarahkoop
Copy link
Contributor

Summary of changes

  • Add VenmoLauncher and associated API to decouple VenmoClient from activity result handling
  • Remove VenmoClient API for activity result handling

The VenmoFragment demo app integration and v5_MIGRATION_GUIDE show what this new integration pattern looks like. I opted to rename the existing tokenizeVenmoAccount method to requestAuthChallenge, then the merchant launches the auth challenge, and passes that result to a new tokenizeVenmoAccount method (previously internal method called onVenmoResult).

I think keeping a tokenizeVenmoAccount method existing, and adding one step the merchant has to take before it makes most sense, but please weigh in if that feels complicated and we should just do new method names for both.

Checklist

  • Added a changelog entry

Authors

@sarahkoop sarahkoop requested a review from a team as a code owner October 5, 2023 21:11
Copy link
Contributor

@sshropshire sshropshire left a comment

Choose a reason for hiding this comment

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

🔥 ! I have a couple q's that are out of scope I'll add in. This is great!

Comment on lines +47 to +51
if (nonce != null) {
handleVenmoAccountNonce(nonce);
} else {
handleError(error);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't relevant to this PR, but worth mentioning. I remember from that one discussion a merchant dev mentioned nullability in callbacks being a hinderance. It may be worth considering a single result type for result callbacks so that null checks aren't required.

I recently thought about having an interface type in PPCP e.g. VenmoResult that would allow logic like:

if (result instanceof VenmoSuccessResult) {
  // handle success
} else if (result instanceof VenmoErrorResult) {
  // handle error
}

There are other ways too, like using enums. Just a thought I wanted to share. We have to figure something out on PPCP too. It'll be a good topic to brainstorm on going forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes I remember that feedback - I think a single result type could be a good move. I'll go forward with these API changes with the dual results for now and create a ticket to reconsider the approach and align across all modules

Comment on lines +40 to +41
venmoLauncher = new VenmoLauncher(this, venmoAuthChallengeResult ->
venmoClient.tokenizeVenmoAccount(venmoAuthChallengeResult, this::handleVenmoResult));
Copy link
Contributor

Choose a reason for hiding this comment

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

More 🧠 🌦️ : I wonder if composing a reference to BraintreeClient within each Launcher would be a good design pattern once ClientTokenProvider is no longer required. It would make a method like venmoLauncher.authenticateVenmoUser() possible. It might be cool, but it might be worth holding off to keep it simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed I have a separate ticket to do this across all the modules - I think it would make most merchant integration stories simpler and we can add a params object for customization if needed

Copy link
Contributor

@jaxdesmarais jaxdesmarais left a comment

Choose a reason for hiding this comment

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

🔥

private lateinit var venmoClient: VenmoClient

@override fun onCreate(savedInstanceState: Bundle?) {
venmoLauncher = VenmoLauncher(this) { authChallengeResult ->
Copy link
Contributor

@scannillo scannillo Oct 9, 2023

Choose a reason for hiding this comment

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

I think of "authentication challenge" as a barrier to protect against unauthorized users, and less so for general login. According to this definition though, a basic password authentication does qualify.

Just food for thought that this language might be confusing since for other flows (3DS) the "challenge" is optional and not required.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe continue or presentLogin or startLogin?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sshropshire what are your thoughts on changing up this naming scheme?

I'd be fine with something like venmoClient.presentLogin returning a LoginResult or keeping with this authChallenge naming. Thinking through the other modules if we want consistent naming:

PayPal and Venmo are "login" screens
Google Pay is just the Google Pay dialog so not sure either naming makes sense for that one (same with PayPalNativeCheckout)
3DS is a true auth challenge
Local Payment is likely a "login" screen
SEPA launches a mandate

Maybe it won't make sense to have naming consistency and we should just describe what each flow is doing? I do like the clarity across integrations of a single pattern though

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeh, I'm with you on preferring a unified name across modules!

Copy link
Contributor

@scannillo scannillo Oct 10, 2023

Choose a reason for hiding this comment

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

I have a question which is slightly tangential, but I think could inform the discussion:

For 3DS, we'll still require merchants to call continuePerformVerification in order to proceed with the challenge?

So would that look something like this (in pseudocode/short-hand)?

class MyActivity {
    fun onCreate() {
        threeDSecureLauncher { authChallengeResult ->
            threeDSecureClient.performVerification() {
                threeDSecureClient.continuePerformVerification()
            }
        }
    }

    func onClick() {
        threeDSecureClient.requestAuthChallenge()
    }
}

class MyClass: ThreeDSecureListener {
    fun onThreeDSecureSuccess() {
        // handle result/nonce
    }
}

Would the launcher & client steps be flipped?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the 3DS case, the performVerification method becomes requestAuthChallenge (or we could leave the performVerification naming as is)

class MyActivity {
    fun onCreate() {
        threeDSecureLauncher = ThreeDSecureLauncher(this) { authChallengeResult ->
              threeDSecureClient.continuePerformVerification() { nonce, error -> 
                  // handle nonce/error
             }
        }
    }

    func onClick() {
        threeDSecureClient.requestAuthChallenge(this, request) { authChallenge, error -> 
          threeDSecureLauncher.launch(authChallenge)
        }
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a ticket to re-asses the naming of the requestAuthChallenge method and make sure it makes sense across modules so I'm going to leave it as is in this PR

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm all for merchants having an intuitive interface. It's a good callout to try and improve naming whenever we can!

I don't have a major preference, but with this new design pattern the Launcher will be presenting the login flow instead of the Client. We talked about potentially allowing the Launcher to have a reference to the Client, that would make methods like launcher.startLogin() possible.

The current naming is tightly coupled to the underlying Android frameworks we use. The way it is now helps us a lot more than it'll help merchants. We should iterate on naming before GA to maximize API clarity.

v5_MIGRATION_GUIDE.md Outdated Show resolved Hide resolved
`BraintreeClient` and `VenmoClient` no longer require references to Fragment or Activity and do not
need to be instantiated in `OnCreate`.

```kotlin
Copy link
Contributor

@scannillo scannillo Oct 10, 2023

Choose a reason for hiding this comment

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

Just an idea - but toward the end of our iOS V6 work, we realized that we could create code diffs in Markdown snippets (like this in the 3DS Section). It's pretty neat and really clear for what lines of code in a sample integration have to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very cool - added that!

*/
public VenmoLauncher(@NonNull Fragment fragment,
@NonNull VenmoAuthChallengeResultCallback callback) {
this(fragment.getActivity().getActivityResultRegistry(), fragment.getViewLifecycleOwner(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Question - is fragment.getViewLifecycleOwner() something that would be acceptable for a merchant to do? Just curious if we need to offer 2 VenmoLauncher inits, or if doing so prevents issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes merchants can call that method!

But if we change the parameters of the method to VenmoLauncher(FragmentActivity activity, LifecycleOwner lifecycleOwner, VenmoAuthChallengeResultCallback callback) it gets a little weird because merchants that are instantiating the class in an activity would call VenmoLauncher(this, this, callback) and pass in their activity twice, and merchants instantiating in a fragment would call VenmoLauncher(getActivity(), fragment.getViewLifeCycleOwner(), callback) - where they shouldn't pass in the activity twice.

It introduces the possibility that if they pass in any other LifecycleOwner that isn't their fragment's view lifecycle owner (i.e if they pass in their activity twice), it wont work. I think there are too many other options for LifecycleOwner that it adds more complication than just having to constructors.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's really helpful, thanks Sarah!

Comment on lines +35 to +42
/**
* Used to launch the Venmo authentication flow to tokenize a Venmo account. This class must
* be instantiated in the OnCreate method of your Activity.
*
* @param activity an Android Activity from which you will launch the Venmo app
* @param callback a {@link VenmoAuthChallengeResultCallback} to receive the result of the
* Venmo app switch authentication flow
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

👏 This is super clear

Copy link
Contributor

@scannillo scannillo left a comment

Choose a reason for hiding this comment

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

No blockers, just a few questions!

@sarahkoop sarahkoop merged commit 5ad6494 into v5 Oct 10, 2023
@sarahkoop sarahkoop deleted the venmo_launcher_api branch October 10, 2023 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants