woocommerce / woocommerce-paypal-payments

https://wordpress.org/plugins/woocommerce-paypal-payments/
GNU General Public License v2.0
62 stars 47 forks source link

improve checkout validation & order creation (448) #513

Closed InpsydeNiklas closed 2 years ago

InpsydeNiklas commented 2 years ago

Describe the Bug

Following update 1.6.2, the PayPal order is created first and the WooCommerce checkout validation & order creation happens afterward. This change was done to improve plugin compatibility and prevent orders from incorrectly receiving a "Failed" status. This change means users can leave all WooCommerce checkout fields empty and click the PayPal button. The user can log into their account, confirm the payment, and only after returning to the checkout page, the user would get an error about the checkout fields being empty. No confirmation email will be sent and it should be obvious the payment was not successful.

But users should not receive an error like this after confirming the payment screen. If possible, errors should always be thrown before. Preferably, it should work like this instead:

To Reproduce

  1. visit the checkout page
  2. leave required fields empty
  3. click PayPal smart button
  4. log in and confirm payment
  5. checkout page will reload with an error about the required fields missing
  6. user can either fill the required fields and start from step 3 again or reload the page, fill the fields and click the "Place order" button, as the payment has already been pre-authorized.

Expected Behavior

Checkout form field validation should occur immediately. Clicking the smart button when not all required fields are set throws an error immediately without letting the user log in to their PayPal account.

Actual Behavior

Checkout form field validation occurs after the PayPal order has been created, letting the user pre-authorize the payment without having provided all details. No WooCommerce order is created.

Environment

Additional Details

We can intercept the PayPal button click using its onclick parameter (like we do already) and then calling actions.reject() like discussed here if we need to stop the payment process (though it seems like it can be used only until this handler finishes, so e.g. sending a request to server and then rejecting will not work).

But the main problem is that it seems like WC does not provide any way to trigger validation. In checkout.js it performs some basic validation after user finishes input (e.g. input lost focus) and also there is validation on the server when submitting the form, probably here. I do not see any way to trigger the server validation separately, especially in a documented/stable way, I mean we can try to e.g. add our ajax endpoint and create a class inheriting WC_Checkout to call its protected methods but we cannot be sure that it works in all supported versions or at least will continue to work + it fires some actions which may break things if something uses them assuming that they happen only during form submission. And the JS validation is also not accessible outside the WC script. In our view, WC should come up with some way to trigger validation.

zerfl commented 2 years ago

Great explanation of the issue. Is there perhaps a workaround available in the meantime while the team is working on this?

Squeezemind commented 2 years ago

Any news about this fix?

ramonolivencia commented 2 years ago

I have the same issue.

simo-git commented 2 years ago

Please increase the priority to solve this bug because the response message appearing at the top of the checkout page after the invalid submission is not helpful for a customer.

Plugin version: 1.7.0 WC checkut message: [INVALID_REQUEST] Request is not well-formed, syntactically incorrect, or violates schema. https://developer.paypal.com/docs/api/orders/v2/#error-INVALID_PARAMETER_SYNTAX

As temporary workaround I've seen that the minimum requirement to receive a correct answer to load the credit card form is verify the billing email is formally correct.

cs9k commented 2 years ago

I would love a solution for this as well, we run some code that prevents checkout if you're below a certain minimum order value, and as you described the validation only triggers after everything PayPal related is done and WooCommerce tries to submit the form, which might cause a lot of confusion depending on the customer, esp. if they get the feeling that they already paid for it.

a-danae commented 2 years ago

@InpsydeNiklas I dug here a bit and pinged the core team. How about calling process_checkout when the PayPal button is clicked? Here, for example. Calling that method triggers the validation and closes the modal on failure.

The implementation in the extension handles the requests differently from how WC does it so this will require some refactoring. If we go this way, we'd have to integrate via the actions and filters inside the process_checkout method, which will probably increase the scope of this issue.

AlexP11223 commented 2 years ago

@a-danae we were doing this before, but it was causing some issues. https://github.com/woocommerce/woocommerce-paypal-payments/pull/341

I think we need some function that only validates the data without creating the order etc.

And as I was saying above I don't see such public API available currently.

I do not see any way to trigger the server validation separately, especially in a documented/stable way, I mean we can try to e.g. add our ajax endpoint and create a class inheriting WC_Checkout to call its protected methods (like validate_checkout) but we cannot be sure that it works in all supported versions or at least will continue to work + it fires some actions which may break things if something uses them assuming that they happen only during form submission. And the JS validation is also not accessible outside the WC script.

perler commented 2 years ago

As other plugins manage to do exactly this, maybe risk a look at their open source code? πŸ™„

AlexP11223 commented 2 years ago

@perler I will look into this, but as I can see so far, this plugin did not need to solve this problem. It does not have PayPal smart buttons like here, it simply uses the standard WC checkout workflow and after checkout is finished it redirects to PayPal.

perler commented 2 years ago

ok, but it checks the checkboxes before it forwards to paypal. isn't this the same routine?

kwisatz commented 2 years ago

Chiming in here to say this is absolutely critical. I cannot understand how this plug-in could have been released in its current state. Even without additional plug-ins adding form fields that would need server-side validation, it seems essential to me that form validation happens before the paypal payment process is initiated which doesn't seem to be the case currently.

InpsydeNiklas commented 2 years ago

The PayPal Plus integration and PayPal Payments have fundamental differences regarding the checkout flow. PayPal Payments implements the PayPal smart buttons with advanced functionality such as Vaulting, making this entire process more complex. The checkout form field validation occurred immediately up until version 1.6.1. You can try it with this old plugin version if this is critical for you. But it's not something we would recommend as this behavior was changed deliberately to improve plugin compatibility until there is a better solution than the original implementation.

perler commented 2 years ago

I kinda understand what you want to accomplish but no customer is coming back from a whole PayPal checkout process with 2FA and other "annoyances" just to getting told that he forgot to check the terms and conditions box and does it again. So this is just not working.

On Mon, 2 May 2022, 21:13 Niklas Gutberlet, @.***> wrote:

The PayPal Plus integration and PayPal Payments have fundamental differences regarding the checkout flow. PayPal Payments implements the PayPal smart buttons with advanced functionality such as Vaulting, making this entire process more complex. The checkout form field validation occurred immediately up until version 1.6.1. You can try it with this old plugin version if this is critical for you. But it's not something we would recommend as this behavior was changed deliberately to improve plugin compatibility until there is a better solution than the original implementation.

β€” Reply to this email directly, view it on GitHub https://github.com/woocommerce/woocommerce-paypal-payments/issues/513#issuecomment-1115263865, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEYD5EVIQBK5RVL3A4GOJTVIASODANCNFSM5PIC66IQ . You are receiving this because you were mentioned.Message ID: @.***>

kwisatz commented 2 years ago

Why not create a two-step process? The first step submits and validates the checkout form. Actually submitting it will allow any third party plug-ins to do their server-side validation too. If the form validates, then, and only then load the paypal sdk and their buttons. I have recently implemented Paypal Payments too and while I agree that it's cumbersome – mostly due to confusing documentation – nothing prevents us from choosing when we want to initialize the process and display the buttons.

I think you've seen the feedback on this plug-in's Wordpress.org page and holding on to the current workflow isn't going to cut it.

kaushikasomaiya commented 2 years ago

User at (5186558-zen) got confused with this behavior.

MysticMedusa commented 2 years ago

It's so flaky at my end that we're only offering Stripe at checkout.

There was error after error after error - revenue down from Paypal payments unable to be processed and extra support time on understandably frustrated customers + myself and devs in fruitless hours spent going around in circles at Paypal. I am extremely stressed by this and it makes me doubt Woo itself - this was forced upon Woo customers in place of a solution that worked. But a non-working checkout kills businesses.

WooCommerce did not disclose the issues with the plugin - if they had told me that even apart from all the bugs, customers would need to complete an extra step, I would have to cancel billing agreements with thousands of customers and that it does not work with Woo Subscriptions, I would have still needed to begin looking for another payment processor (having been with Woo Commerce since 2016) but I would have been doing so with significantly less stress.

a-danae commented 2 years ago

Why not create a two-step process? The first step submits and validates the checkout form. Actually submitting it will allow any third party plug-ins to do their server-side validation too. If the form validates, then, and only then load the paypal sdk and their buttons.

Hey @kwisatz. Thanks for the suggestion.

If I'm getting you correctly, to trigger the first step, the customer would have to click on some "submit" button signaling that the form has been filled and ready to be submitted. Then the form would be submitted. Then they'd see and click on the "PayPal" button to make the payment, and the form would be submitted again with the payment information included.

As far as I see, doing this double submission would trigger a flow similar to what I proposed above bringing back the problems mentioned by @AlexP11223 above, and for which this approach was updated.

Glad to learn if that's not the case in your experience.

AlexP11223 commented 2 years ago

@a-danae I think this suggestion is about doing it similarly to the old PayPal Plus plugin. First the user finishes the standard WC checkout, and then there is a separate page with PayPal buttons to make the payment for the created WC order.

a-danae commented 2 years ago

I think this suggestion is about doing it similarly to the old PayPal Plus plugin.

@AlexP11223 gotcha. Then scratch my last comment.

From a short conversation with the core team and as another option... How about shifting to using the Store API? Basically, you would update the cart manually using the cart API, and then use the checkout's get checkout method to get draft order information. You can then process payment, and if the payment is successful, create the order using process order endpoint.

This would require some more effort. But even if the validation method gets public, this is the recommended approach in the long term.

MysticMedusa commented 2 years ago

After a long to and fro with Woo, went back to Paypal Standard which works perfectly but of course Woo said they don't support it and it could break with any Woo update. They said they will continue to "recommend" paypal payments for its "superior features"

So if, like most Woo customers, you want to use Paypal with Woo, they present you with a choice between:

(1) A functioning plugin that collects revenue/retains customers but could break at any time because Woo no longer support it

(2) A semi-functional plugin that works haphazardly, loses customers who understandably don't want three-step checkouts, adds several hours of customer support problem solving time a week, generates wonky data in Woo reports - eg: money not collected shows as revenue, does not work with Woo Subscriptions unless you cancel all your members and re-subscribe them.

Having said that, I don't want to do this with thousands of members but i tried on staging and it didn't work anyway.

Woo support were really pleasant but really, this is apparently as good as it gets - they should have warnings on Woo Subs that you won't be able to do it using Paypal.

bwalger commented 2 years ago

+1 waiting for the update. We lose many orders due to this strange behaviour of plugin. I can understand that it is the "fault" of the user not checking or filling out necessary fields. But a good and usability tested plugin should not allow going to checkout in this status.

--> In my eyes this is a critical missing feature and we cannot wait for WC for a solution. We cannot wait and drink tea :-(

Dinamiko commented 2 years ago

Fixed by #675 and #680.

InpsydeNiklas commented 2 years ago

There's a new pre-release for the 1.8.2 update with a basic validation available here. This basic validation should help prevent confusing behavior while a better long-term solution remains under investigation.

bizrockman commented 2 years ago

I have version 1.9.0 installed, but still I can hit the paypal button without filling out any of the required fields. What leads to that issue descriped in this topic.

I also tried version 1.9.1-test1, but without any change.

As a quick hack I activated the old PayPal option in Woocommerce - functions.php (wp-content/themes/YourTheme/functions.php)

add_filter( 'woocommerce_should_load_paypal_standard', '__return_true' );

Not that nice like this module, but it works as a fallback and quick hack until this issue is solved.

anton-esin commented 1 year ago

@InpsydeNiklas What is the basic validation? We leave the address and name fields empty and it will still allow to hit paypal buttons. Is it intended behavior right? We have Plugin version 1.9.3. Wordpress: 6.0.3. Woocommerce 6.7.0.

AlexP11223 commented 1 year ago

@anton-esin Are these the standard WC fields? Do you see any errors in the DevTools console?

anton-esin commented 1 year ago

@AlexP11223 Yes those are standard WC fields. And no I don't see any errors in the DevTools console.

MysticMedusa commented 1 year ago

We could not get it to work and it was taking up so much time with devs and customer support we reverted to Paypal standard but it's a security risk as not supported so dropped Paypal altogether and just use Stripe now. Revenue _is _down as many people prefer PP for some reason but the plugin is problem-free.

Still mystified as to why WC made this plugin compulsory when it has so many bugs, does not work with Woo Subs etc etc

On 2022-10-19 05:17, Anton Esin wrote:

@InpsydeNiklas [1] What is the basic validation? We leave the address and name fields empty and it will still allow to hit paypal buttons. Is it intendet behavior right? We have version 1.9.13.

-- Reply to this email directly, view it on GitHub [2], or unsubscribe [3]. You are receiving this because you commented.Message ID: @.***>

Links:

[1] https://github.com/InpsydeNiklas [2] https://github.com/woocommerce/woocommerce-paypal-payments/issues/513#issuecomment-1283689534 [3] https://github.com/notifications/unsubscribe-auth/APXIOQ3CNSXXPV3DUC6NJD3WD64A5ANCNFSM5PIC66IQ

nroccogit commented 1 year ago

Hi, I'm using the action "woocommerce_checkout_process" to validate some custom checkout fields. For example I've date of birth to check because customer needs to be at least 18 years old to buy. The user can click the Paypal Buttons on checkout and proceed with the order because the custom validation hooked on action "woocommerce_checkout_process" doesn't run as usual with other payments methods that use the "standard woocommerce #place_order" button. Now I need to disable Paypal. Does anyone has a fix for this?

This is a sample of my code:

add_action('woocommerce_checkout_process', 'my_custom_checkout_validation', 1);
function my_custom_checkout_validation() {
    if( isset($_POST['billing_birth_date']) && ! empty($_POST['billing_birth_date']) ){
        // Get customer age from birthdate
        $age = date_diff(date_create($_POST['billing_birth_date']), date_create('now'))->y;
        // Checking age and display an error notice avoiding checkout 
        if( $age < 18 ){
            wc_add_notice('Age ERROR', "error" );
        }
    }
}
AlexP11223 commented 1 year ago

@nroccogit try using woocommerce_after_checkout_validation instead.

nroccogit commented 1 year ago

@nroccogit try using woocommerce_after_checkout_validation instead. @AlexP11223 Thanks!!! It Works!!

If anyone is interested (you can use also $_POST):

add_action('woocommerce_after_checkout_validation', 'my_customer_checkout_validation', 10, 2);
function my_customer_checkout_validation($data,$errors) {
    if( isset($data['billing_birth_date']) && ! empty($data['billing_birth_date']) ){
        // Get customer age from birthdate
        $age = date_diff(date_create($data['billing_birth_date']), date_create('now'))->y;
        // Checking age and display an error notice avoiding checkout 
        if( $age < 18 ){
            wc_add_notice('Age ERROR', "error" );
        }
    }
}