-
Notifications
You must be signed in to change notification settings - Fork 123
feat: Use Venmo One Time Payment Session Hook #702
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
feat: Use Venmo One Time Payment Session Hook #702
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.
looks good. what's the testing strategy here? this wouldn't take long to test.
| return ref.current; | ||
| } | ||
|
|
||
| export function useProxyProps<T extends Record<PropertyKey, unknown>>( |
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.
How do we want to deal with duplicate code like this?
The original implementation has tests too: https://github.com/paypal/paypal-js/blob/main/packages/react-paypal-js/src/hooks/useProxyProps.test.ts
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.
Is there a limitation to importing code from the old react implementation?
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.
@kand had an idea to maybe create a shared utils folder between the v6 implementation and v5 implementation. We could pursue something like that 🤔
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.
Currently the V6 specific tsconfig instructs rollup to not allow imports outside of the v6 directory. In a discussion yesterday we talked about including a barrel file to export this methods for v6 usage. We can also update the v6-specific tsconfig to allow importing this hook specifically or more hooks generally.
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.
Ok, discussed this with @gregjopa and he suggested that we go with a WET approach in order to make V5 "very deletable" in the future.
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.
Alright, sounds good. Did you mention yesterday we are sharing other stuff from v5 currently though? Do we need to duplicate that code?
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 moved this util into the base branch so we can all share it while we work on our hooks.
https://github.com/paypal/paypal-js/pull/695/files#r2440686418
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.
Did you mention yesterday we are sharing other stuff from v5 currently though? Do we need to duplicate that code?
@kand In a prior iteration of the loadCoreSdkScript function, it was a nearly identical method to the loadScript function that V5 leverages. So it was basically copy-pasted over for V6 to use. I mentioned this as an example of the WET approach we're currently taking.
|
|
||
| useEffect(() => { | ||
| if (!sdkInstance) { | ||
| throw new Error("no sdk instance available"); |
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.
How do we want to teach error handling? I think a hook throwing versus returning an error state forces a specific integration pattern, because it dictates where a merchant needs to place error boundaries.
An error state allows merchant to conditionally handle gracefully in the consuming component versus in a higher error boundary. If that's what we want, then we need our examples to show wrapping the components that do this setup in an error boundary (and we should probably provide this error boundary component too).
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.
Ahh that's a good callout @imbrian! I think the v5 implementation also relies on throwing an error right? If that's the case we may want to follow the same pattern here and teach using ErrorBoundary
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 like the idea of returning some kind of error state rather than throwing an error. Requiring merchants to use an <ErrorBoundary> in a specific place (i.e. outside the component using the hook) feels unnecessarily restrictive.
| }: UseVenmoOneTimePaymentSessionProps): UseVenmoOneTimePaymentSessionReturn { | ||
| const { sdkInstance } = usePayPal(); | ||
| const sessionRef = useRef<VenmoOneTimePaymentSession | null>(null); | ||
| const proxyCallbacks = useProxyProps(callbacks); |
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.
Yeah, I see what you're doing. Leveraging the stable ref in useProxyProps to satisfy the useeffect dependency array below, while allowing the underlying functions to change. It's the same use case as v5.
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 good to hear @imbrian! It's an awesome hook to use 🙂
packages/react-paypal-js/src/v6/types/VenmoOneTimePaymentSessionTypes.d.ts
Show resolved
Hide resolved
| if (sessionRef.current) { | ||
| sessionRef.current.destroy(); | ||
| sessionRef.current = null; | ||
| } |
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.
Would it work to shorten these lines to the following?
| if (sessionRef.current) { | |
| sessionRef.current.destroy(); | |
| sessionRef.current = null; | |
| } | |
| sessionRef.current?.destroy(); | |
| sessionRef.current = null; |
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.
Could this entire return even just be return handleDestroy? Might be clearer that this is the exact same behavior.
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 that's a great suggestion @kand ! Thank you!
packages/react-paypal-js/src/v6/types/VenmoOneTimePaymentSessionTypes.d.ts
Show resolved
Hide resolved
|
|
||
| export type UseVenmoOneTimePaymentSessionProps = | ||
| | (Omit<VenmoOneTimePaymentSessionOptions, "orderId"> & { | ||
| createOrder: () => VenmoOneTimePaymentSessionPromise; |
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.
Not a part of this PR, but out of curiosity, do we need a different type for VenmoOneTimePaymentSessionPromise? It appears to be equivalent to BasePaymentSessionPromise.
Edit: Slack thread here
| mockSdkInstance.createVenmoOneTimePaymentSession, | ||
| ).toHaveBeenCalledWith({ | ||
| orderId: "test-order-id", | ||
| onApprove: expect.any(Function), |
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.
Is expect.any(Function) leaving a mock function in place? All good, but I've usually seen jest.fn() in place of this or an empty arrow function () => {}.
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 believe this is the check to see if createVenmoOneTimePaymentSession was called with the right things, if we have jest.fn() in place of this then it will return a new mock function which will fail the equality check I believe 🤔
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, sorry, I saw this usage in a few places and glossed over this being used in expect. There are several locations though, where I think the AI got confused and is using this where you'd typically see jest.fn(). I'll highlight one of those locations in another comment.
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.
Oh are you talking about passing props
paypal-js/packages/react-paypal-js/src/v6/hooks/useVenmoOneTimePaymentSession.test.ts
Line 441 in 8b7af4e
| onApprove: expect.any(Function), |
jest.fn()
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.
Good call @kand !
packages/react-paypal-js/src/v6/hooks/useVenmoOneTimePaymentSession.test.ts
Show resolved
Hide resolved
packages/react-paypal-js/src/v6/hooks/useVenmoOneTimePaymentSession.test.ts
Outdated
Show resolved
Hide resolved
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.
Nice work @hamzanasir!
| await expect( | ||
| act(async () => { | ||
| await result.current.handleClick(); | ||
| }), | ||
| ).rejects.toThrow("Venmo session not available"); |
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.
| await expect( | |
| act(async () => { | |
| await result.current.handleClick(); | |
| }), | |
| ).rejects.toThrow("Venmo session not available"); | |
| expect( | |
| act(() => { | |
| result.current.handleClick(); | |
| }), | |
| ).rejects.toThrow("Venmo session not available"); |
I'm fairly certain async / await isn't needed here. My tests didn't end up needing it. Though, I am a bit confused by the sparse documentation on act(). In this example, async / await is used, but not the inner await; i.e. in this case, remove the await in await result.current.handleClick().
https://legacy.reactjs.org/docs/testing-recipes.html#data-fetching
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.
Hmm for some reason my tests seem to fail if I don't use async / await. Using async / await makes sense to me since handleClick is an async function 🤔
| onApprove: expect.any(Function), | ||
| onCancel: expect.any(Function), | ||
| onError: expect.any(Function), |
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.
Here's an example where we should be using jest.fn() rather than expect.any(Function). There are several more of these.
| }); | ||
|
|
||
| describe("callback proxying", () => { | ||
| test("should proxy callbacks correctly through useProxyProps", () => { |
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 my tests, I mocked out useProxyProps:
Which I think is slightly more robust than the unit test here because this test isn't checking that the functions went through useProxyProps; for example, createSessionCall.onApprove could be true here and the test would pass. What do you think?
| VenmoOneTimePaymentSessionPromise, | ||
| } from "@paypal/paypal-js/sdk-v6"; | ||
|
|
||
| export type UseVenmoOneTimePaymentSessionProps = |
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.
As this type is defined, I'm not sure if the hook can accept fullPageOverlay as an argument. I was taking another look at this while looking into the redirect and direct-app-switch presentation modes for PayPal.
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 a great callout @EvanReinstein! I think a follow up ticket would be great to get parity on all the different types of start options we can pass 🙂
feat: Use Venmo One Time Payment Session Hook
This PR implements the venmo one time payment session hook, the proposed usage for this hook would be as follows:
Then all the merchant needs to do is pass the click handler to the respective button: