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

[Bug] Different behaviour when a user closes the popup modal #613

Open
xt-riot opened this issue Jan 24, 2025 · 0 comments
Open

[Bug] Different behaviour when a user closes the popup modal #613

xt-riot opened this issue Jan 24, 2025 · 0 comments

Comments

@xt-riot
Copy link

xt-riot commented Jan 24, 2025

Library used

paypal-j, react-paypal-js and direct implementation

🐞 Describe the Bug

Whenever there is an asynchronous operation in the onClick hook, the PayPal modal appears with a loading screen. If the user closes the modal before the async operation resolves, the onError callback is executed. The error passed to that callback is Window is closed, can not determine type. The onCancel is not called

If the asynchronous operation happens on the createOrder hook and the customer closes the modal before the login or account page has fully loaded(see additional information to what I believe is the difference), both the onCancel and onError callbacks are executed. However, the onError gets a different error: Detected popup close

On the other hand, if the customer closes the popup when the operation has finished(therefore the customer is on the login or account page), the onCancel callback is executed.

🔬 Minimal Reproduction

Code to reproduce
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>PayPal example</title>
    </head>
    <body>
        <div id="paypal-button-container"></div>
        <script src="https://www.paypal.com/sdk/js?client-id=test&components=buttons&currency=EUR"></script>
        <script>
            paypal.Buttons({
                async onClick(data, actions) {
                    return new Promise(resolve => {
                        setTimeout(() => {
                            resolve()
                        }, 1000)
                    }).then(d => {
                        return actions.resolve()
                    }).catch(err => {
                       return actions.reject()
                    })
                },
                async createOrder(data, actions) {
                    return new Promise(resolve => {
                        return resolve(actions.order.create({
                            purchase_units: [{
                                reference_id: "some_ref",
                                description: "Some description",
                                custom_id: "some_custom_id",
                                amount: {
                                    currency_code: "EUR",
                                    value: "200.00",
                                    breakdown: {
                                        item_total: {
                                            currency_code: "EUR",
                                            value: "200.00"
                                        }
                                    }
                                }
                            }]
                        }))
                    })
                },
                onApprove(data, actions) {
                    // console.log('onApprove callback', data)
                    return actions.order.capture().then(function(details) {
                        // No need to do anything, the example wont get here
                        // console.log('order captured', details)
                    })
                },
                onCancel(data) {
                    console.log('user cancelled paypal', data)
                },
                onError(err) {
                    console.log('paypal error', err)
                },
            }).render("#paypal-button-container");
        </script>
    </body>
</html>
Image when cancelling without the overlay(no onCancel, only onError)

Image

Image when cancelling with the overlay(both onCancel and onError)

Image

Image when cancelling on the login page(only onCancel, no onError)

Image

Video
Screen.Recording.2025-01-24.at.2.45.02.PM.mov

😕 Actual Behavior

The onError and onCancel callbacks are executed differently on each case.

Case 1 (user closes popup - no overlay visible)
Only onError is called with the error.message being Window is closed, can not determine type

Case 2 (user closes popup - overlay visible)
Both onCancel and onError is called:

  • onError is called with the error.message being Detected popup close
  • onCancel is called with the canceled order_id

Case 3 (user closes popup when the paypal page has fully loaded - user is on login or account page)
Only onCancel is called with the canceled order_id

🤔 Expected Behavior

As a developer, I would expect the onCancel callback to be executed if the customer has closed the popup modal during the loading spinner/validation. If that is not possible, I would expect the error message to be the same, so I could perform my own actions should the customer abort willingly.

🌍 Environment

  • Node.js/npm: No node, bare HTML. But also tested in node 18 and 20
  • OS: macOS Sequoia 15.1.1
  • Browser: Google Chrome 132.0.6834.110 (Official Build) (x86_64) and Mozilla Firefox 134.0.2 (64-bit)

➕ Additional Context

I believe that the overlay is shown when the createOrder has resolved with an order_id and right before the modal gets redirected to the paypal login/account page. Before the order_id(ie, before createOrder has resolved), I believe that the overlay is not rendered. However, that is only my guess based on some tests I've run, so it could be far from the actual code implementation/behaviour.

The video should better describe what I'm trying to say.

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

No branches or pull requests

1 participant