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

[Request] Disable PayPal Smart buttons for specific product types #234

Closed
jimjasson opened this issue Aug 26, 2021 · 16 comments · Fixed by #353
Closed

[Request] Disable PayPal Smart buttons for specific product types #234

jimjasson opened this issue Aug 26, 2021 · 16 comments · Fixed by #353

Comments

@jimjasson
Copy link

Describe the feature/request

Our team, SomewhereWarm, is looking to add an integration between our plugins and the new PayPal Payments. Some of the extensions we develop are Product Bundles, Gift Cards, Composite Products and All Products for WooCommerce Subscriptions -- all are available on WooCommerce.com.

These 4 extensions allow customers to make configurations in the single product page that can result to

  1. a price change or,
  2. additional data that need to be posted, such as Gift Card meta (sender, recipient, message).

We have found that currently PayPal Smart buttons do not parse these prices/data correctly when they are clicked. Therefore, to prevent any issues we thought to add an integration that disables these buttons for specific product pages -- we used to have a similar integration for PayPal Checkout.

Taking a look at the code, we noticed that you are already hiding these buttons for specific product types, such as Grouped and External:

$product->is_type( array( 'external', 'grouped' ) )

Ideally, we would like to filter the return value of these two functions:

and
Do you think it is possible to add a filter there that has the $product object as an argument?

So far, we have tried a couple of approaches to disable these buttons. However, none of them was robust or performant enough!

The first thing we tried was to remove this action:

-- however, there wasn't any function we could use to retrieve the SmartButton object.

Another thing we tried was to use the woocommerce_paypal_payments_single_product_renderer_hook and return an empty string. However, the $product object wasn't available at this point and running wc_get_product() on every page load wasn't a performant solution.

In case there is any way to achieve what we are after without introducing new filters, please let us know! Thank you in advance!

@helgatheviking
Copy link
Contributor

Seconded for me as the author of Mix and Match Products and Name Your Price. The ideal would be that the buttons can be dynamically modified to reflect the custom prices... but in the absence of that they need to be disabled.

@helgatheviking
Copy link
Contributor

As a followup, I will note that the PayPal Express Checkout plugin had javascript triggers for enabling and disabling the buttons. So they could be dynamically disabled when the complex product type's configuration was invalid and re-enabled when valid.

@helgatheviking
Copy link
Contributor

As another follow up :) it's not possible to conditionally filter woocommerce_paypal_payments_single_product_renderer_hook based on the $product object as the global $product isn't available when the button_renderer() render callback is added to the woocommerce_single_product_summary hook from the wp hook.

@InpsydeNiklas
Copy link
Member

Thanks for your feedback. We are looking into this and will get back with more details.
I don't think it's possible as of now, but we plan to extend on what was introduced in #203.

@helgatheviking
Copy link
Contributor

@InpsydeNiklas I was looking at this again today.

It appears that there's this code ButtonsToggleListener listening to whether the add to cart button has the disabled class or not and hiding/showing the paypal buttons based on that. But it looks like it's only running when the product is a variable product.

Is there a way we could get that same listener to fire on custom product types? Mix and Match already toggles the disabled class on the add to cart button when the container config is not valid. Whether all the selected products would end up in the order is another question. :)

@helgatheviking
Copy link
Contributor

helgatheviking commented Sep 24, 2021

@InpsydeNiklas in the short term I have an idea on how to remove the buttons conditionally at least for most users, but can't figure out how to get the instance of the SmartButton class. Can you advise how to access that?

function kia_remove_paypal_buttons() {
    global $product;

    if ( $product->is_type( 'bundle' ) ) {

        $button = SmartButton(); // How do we get this instance?

        // Since single_product_renderer_hook() is private...
        $hook = (string) apply_filters( 'woocommerce_paypal_payments_single_product_renderer_hook', 'woocommerce_single_product_summary' );

        remove_action( $hook, array( $button, 'button_renderer' ), 31 );
    }
}
add_action( 'woocommerce_before_single_product', 'kia_remove_paypal_buttons' );

@AlexP11223
Copy link
Contributor

AlexP11223 commented Oct 19, 2021

how to access that?

You can do it by accessing the container like shown in #203 and getting the button.smart-button service.

@helgatheviking
Copy link
Contributor

helgatheviking commented Oct 19, 2021

Thanks @AlexP11223. Showing the limits of my skill here, but I find the code you linked to is quite difficult to read for those of us used to the traditional way of writing plugins. The example seems to show how to add a new service, so I'm still very unclear how to fetch an existing service.

@AlexP11223
Copy link
Contributor

AlexP11223 commented Oct 19, 2021

$button = $c->get('button.smart-button');

inside run of the module that you added (or e.g. inside any action handler added there, like shown in the example).

@helgatheviking
Copy link
Contributor

What is $c? How do I access that? I'm not using the same module interface that this plugin uses. Is it not possible to access directly inside my above snippet?

@AlexP11223
Copy link
Contributor

AlexP11223 commented Oct 19, 2021

You don't need to use this modular architecture for your plugin, you can just create a basic module and register it via woocommerce_paypal_payments_modules like shown in #203.

Then its' run function will be called during WC PayPal Payments plugin initialization, and you can add woocommerce_before_single_product there like you want, and access the button.

public function run(ContainerInterface $c): void
{
	add_action('woocommerce_before_single_product', function() use ($c){
		$button = $c->get('button.smart-button');

		$hook = (string) apply_filters( 'woocommerce_paypal_payments_single_product_renderer_hook', 'woocommerce_single_product_summary' );

		remove_action( $hook, array( $button, 'button_renderer' ), 31 );
	});
}

@helgatheviking
Copy link
Contributor

helgatheviking commented Oct 22, 2021

Another option might be if you added a hook with the $product object in the smart button class:

$product->is_type( array( 'external', 'grouped' ) )

	/**
	 * Renders the HTML for the buttons.
	 */
	public function button_renderer() {
		$product = wc_get_product();
		if (
			! is_checkout() && is_a( $product, \WC_Product::class )
			&& (
				apply_filters( 'woocommerce_paypal_payments_single_product_disable_render', $product->is_type( array( 'external', 'grouped' ) ), $product )
				|| ! $product->is_in_stock()
			)
		) {
			return;
		}

		echo '<div id="ppc-button"></div>';
	}

then plugins can turn the buttons off.

For quick testing:

add_filter( 'woocommerce_paypal_payments_single_product_disable_render', '__return_true' );

for testing with Mix and Match Products or Product Bundles

function kia_disable_paypal_request_buttons( $disable, $product ) {
  if ( $product->is_type( 'bundle', 'mix-and-match' ) ) {
      $disable = true;
   }
   return $disable;
}
add_filter( 'woocommerce_paypal_payments_single_product_disable_render', 'kia_disable_paypal_request_buttons', 10, 2 );

That's going to be a lot simpler from my end and completely eliminates any need for modules or dhii/module-interface packages.

I'm noticing the need for something similar with the "pay later" link as well. Seems silly to show the messages on a single product page when the request buttons aren't visible:

image

@helgatheviking
Copy link
Contributor

We should not need to require dhii/module-interface in order to interact with your plugin though. That feels unnecessarily complex.

@AlexP11223
Copy link
Contributor

You probably don't need to require it in composer if this code executes only when our plugin is active, because there is no isolation of dependencies in WP and it would be loaded by our plugin already.

@helgatheviking
Copy link
Contributor

You might be right, but I am prepared to admit your codebase is too advanced/modern for me. It doesn't look or behave like any other plugin I've ever seen. Most of my other integrations are way simpler. Something like the following where we have 1 filter/callback and check the product object to see if it supports PRB or not would be ideal:

function can_show_buttons( $can, $product ) {
    if ( $product->is_type( 'mix-and-match' ) ) {
        $can = false;
    }
    return $can;
}
add_filter( 'some_filter_name', 'can_show_buttons', 10, 2 );

PayPal is attaching the render hooks very early (on the wp hook I believe). They can't be conditionally removed and you can't access the name of the hook they are added too without mimicking the whole module architecture. This is pretty hard to work with from my side.

Jason's team has resorted to using CSS to hide the buttons and I will probably have to do the same.

@EdithAllison
Copy link

My use case is slightly different but I think we need the same solution. On the site I'm working on we have a Minimum Order Value where checkout buttons are disabled unless MOV amount is met.

I like the solution suggested by #353

And this seems similar to strategy (1) suggested in https://inpsyde.com/en/remove-wordpress-hooks/ for removing closures from hooks.

Something that would allow me to add to my theme:

add_filter( 'some_class_enable', '__return_false' );

woud be a great help. So that I can write code like so:

if ( WC()->cart->subtotal_ex_tax < $minimum  ) {	
    // disable PP button here 
}

Linked: https://wordpress.org/support/topic/programatically-disable-pp-button/#post-15245620

In my case I'd need the ability to disable a button in Mini Cart, Cart page, and Checkout page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants