-
Notifications
You must be signed in to change notification settings - Fork 241
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
Conversation
There was a problem hiding this 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!
if (nonce != null) { | ||
handleVenmoAccountNonce(nonce); | ||
} else { | ||
handleError(error); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
venmoLauncher = new VenmoLauncher(this, venmoAuthChallengeResult -> | ||
venmoClient.tokenizeVenmoAccount(venmoAuthChallengeResult, this::handleVenmoResult)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
v5_MIGRATION_GUIDE.md
Outdated
private lateinit var venmoClient: VenmoClient | ||
|
||
@override fun onCreate(savedInstanceState: Bundle?) { | ||
venmoLauncher = VenmoLauncher(this) { authChallengeResult -> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
}
}
}
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
`BraintreeClient` and `VenmoClient` no longer require references to Fragment or Activity and do not | ||
need to be instantiated in `OnCreate`. | ||
|
||
```kotlin |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool - added that!
Venmo/src/test/java/com/braintreepayments/api/VenmoLauncherUnitTest.java
Outdated
Show resolved
Hide resolved
Venmo/src/test/java/com/braintreepayments/api/VenmoLauncherUnitTest.java
Show resolved
Hide resolved
Venmo/src/test/java/com/braintreepayments/api/VenmoLauncherUnitTest.java
Show resolved
Hide resolved
*/ | ||
public VenmoLauncher(@NonNull Fragment fragment, | ||
@NonNull VenmoAuthChallengeResultCallback callback) { | ||
this(fragment.getActivity().getActivityResultRegistry(), fragment.getViewLifecycleOwner(), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
/** | ||
* 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 | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏 This is super clear
There was a problem hiding this 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!
…tTest.java Co-authored-by: scannillo <[email protected]>
…tTest.java Co-authored-by: scannillo <[email protected]>
…tTest.java Co-authored-by: scannillo <[email protected]>
Summary of changes
VenmoLauncher
and associated API to decoupleVenmoClient
from activity result handlingVenmoClient
API for activity result handlingThe
VenmoFragment
demo app integration andv5_MIGRATION_GUIDE
show what this new integration pattern looks like. I opted to rename the existingtokenizeVenmoAccount
method torequestAuthChallenge
, then the merchant launches the auth challenge, and passes that result to a newtokenizeVenmoAccount
method (previously internal method calledonVenmoResult
).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
Authors