[PIWOO-792] Update PayPal button logic: restrict physical items #1195
Open
Biont wants to merge 7 commits intodev/developfrom
Open
[PIWOO-792] Update PayPal button logic: restrict physical items #1195Biont wants to merge 7 commits intodev/developfrom
Biont wants to merge 7 commits intodev/developfrom
Conversation
…al item handling Refactor `canMakePayment` checks to filter out carts containing physical products. Introduce a WC Store API extension to expose the `is_virtual` flag for enhanced product-level validation. Optimize Express Button registration logic to ensure compatibility and correctness.
- Replace `getCartItems` with `getCartData().items` for more comprehensive cart data extraction. - Add fallback check for missing subtotal element in `calculateTotal` to handle non-classic cart pages.
mmaymo
approved these changes
Apr 15, 2026
… and move virtual item checks for Express Button registration to `canMakePayment` for reactive updates.
mmaymo
approved these changes
Apr 15, 2026
…to AssetsModule Refactor the `is_virtual` flag registration to consolidate API-related logic within `AssetsModule`, improving implementation clarity and reducing duplication.
- Replace `return Infinity` with a thrown error when the `cart-subtotal` element is not found. - Add a try-catch block in `underRange` to handle non-classic cart pages gracefully.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restrict PayPal Express button to virtual products only
Problem
The PayPal Express button (and its "— OR —" divider in the block cart) was appearing for physical products. It should only appear for virtual products, because PayPal Express bypasses the standard WooCommerce checkout address-collection flow — a workflow that is unsuitable for orders that require physical shipping.
The root cause is a reliance on
needs_shipping/WC_Cart::needs_shipping()/WC Blocks'
getNeedsShipping()as the "show button?" signal. These helpers short-circuit tofalsewhen:On such sites every cart appears non-shipping — even carts containing physical products — so the button was unconditionally shown.
The regression was made more visible by the bootstrap timing fix in
bec6d738, which deferred button initialisation towoocommerce_init. Before that commit, the PayPal gateway was sometimes absent from the registered gateways at bootstrap time, suppressing the button as a side-effect.Solution
PHP — classic cart and product page (
PayPalExpressButton.php)Replace all
needs_shippingcalls withWC_Product::is_virtual()checks:mollieWooCommerceCheckIfNeedShipping()helper is replaced by a newisVirtualProduct(\WC_Product)method. For simple products this returns$product->is_virtual(). For variable products it returnstrueif at least one available variation is virtual, so the button appears on product pages where a virtual variant can be selected.$cart->needs_shipping()is replaced by iterating over$cart->get_cart_contents()and calling$product->is_virtual()on each item; the button is suppressed as soon as any physical item is found.JS — WC Blocks express registration (
resources/js/src/checkout/blocks/index.js)The
express_payment_methodsfilter controls whetherregisterExpressPaymentMethodis called at all. This filter now gates only on PayPal being configured and enabled (isExpressEnabled); it no longer inspects cart contents. Cart-content gating at registration time was deliberately avoided:registerExpressPaymentMethodis called once at page load and cannot be re-invoked if items are later removed, so a page-load cart snapshot would prevent the button from appearing after a physical item is removed without a full reload.The
canMakePaymentcallback inexpress_payment_method_argscarries the virtual-only check. WC Blocks callscanMakePaymentreactively on every cart mutation. The callback reads item data directly from the store viaselect('wc/store/cart').getCartData().items— thecartItemsargument that WC Blocks passes tocanMakePaymentdoes not carry Store API extension fields, so the store must be read explicitly. When the cart changes from mixed to all-virtual,canMakePaymentreturnstrueand both the button and the "— OR —" divider appear without a page reload. When any physical item is present, it returnsfalseand WC Blocks suppresses the entire express payment section.WC Store API extension (
PayPalExpressButton::registerStoreApiExtension())A new
woocommerce_store_api_register_endpoint_dataregistration exposes avirtualboolean per cart item under themollie-paymentsnamespace:This makes
WC_Product::is_virtual()available to JavaScript through the standard WC Blocks cart store (select('wc/store/cart').getCartData().items), so the JS layer never has to infer product type from shipping configuration.JS — classic-cart and product-page scripts (
paypalButtonCart.js,paypalButton.js)Both scripts are enqueued unconditionally on their respective pages, but the PHP layer now only renders
#mollie-PayPal-buttonwhen the product/cart qualifies (all-virtual). Two defensive guards were added:paypalButtonCart.js—calculateTotal()now returnsInfinitywhen the.cart-subtotalDOM element is absent (i.e. the page is using the block cart, which has no classic-cart markup).InfinitymeansminFee > Infinityis alwaysfalse, so the script never attempts to hide the button.paypalButton.js— an earlyreturnis added afterdocument.querySelector('#mollie-PayPal-button')when the element isnull(i.e. the product is physical and PHP did not render the button).Rationale:
is_virtualvsneeds_shippingneeds_shipping/getNeedsShipping()is a logistics concern: it answers "does this cart require a shipping method to be selected?" — a question that depends on store configuration (globally enabled shipping, configured zones, product shipping classes). It is a valid guard for blocking checkout progression but is not a reliable product-type signal.is_virtualis a product attribute: it is set on the product itself and is independent of shipping configuration. It answers "is this product delivered digitally?" — which is exactly the question the PayPal Express gating needs answered.PayPal Express is only appropriate for virtual goods because:
needs_shippingcreates a false equivalence between "shipping is disabled" and "the product is digital", which breaks on any site that sells mixed or physical catalogues with shipping globally disabled.