woocommerce-gateway-paypal-express-checkout
woocommerce-gateway-paypal-express-checkout copied to clipboard
Caching Plugin and Nonce Lifespan causing "secure browser" popup to fail
Describe the bug When using WP Rocket, a page-caching plugin (www.wp-rocket.me), caching of individual product pages cause the "buy now" button to fail - opening the "Secure Browser", but closing with a JS error; due to the nonce in use expiring very quickly.
To Reproduce
- Enable a caching plugin (I use WP Rocket, I'm sure others will show the same issue)
- Wait for cache to expire (not sure on timings, see below)
- Try to click "Buy Now" button on a product page
- "Secure browser" window pops up to log in to PayPal with spinner graphic
- "Secure browser" window closes and javascript error present in console
- XHR response from ? is "Cheatin' huh"?
Expected behavior The secure browser window should open as expected to permit a standard checkout flow.
Console Log
logger.js:63 ppxo_unhandled_error {stack: "Error: TypeError: Cannot read property 'messages' …paypalobjects.com/api/checkout.4.0.286.js:4327:9)", errtype: "[object Error]", timestamp: 1569327768192, windowID: "08aba31bbc", pageID: "bff1009b6b", …}
print @ logger.js:63
log @ logger.js:181
error @ logger.js:234
(anonymous) @ setup.js:36
(anonymous) @ exceptions.js:27
(anonymous) @ promise.js:122
setTimeout (async)
_proto.reject @ promise.js:120
_loop @ promise.js:171
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
_loop @ promise.js:171
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
_loop @ promise.js:178
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
(anonymous) @ promise.js:207
_loop @ promise.js:176
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
(anonymous) @ promise.js:51
respond @ client.js:147
_RECEIVE_MESSAGE_TYPE.<computed> @ types.js:126
receiveMessage @ index.js:114
messageListener @ index.js:140
types.js:121 Uncaught Error: TypeError: Cannot read property 'messages' of undefined
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:625
at _loop (5be91ae8aef9b3b73997b1f9cbcc7375.js:62)
at ZalgoPromise._proto.dispatch (5be91ae8aef9b3b73997b1f9cbcc7375.js:63)
at ZalgoPromise._proto.resolve (5be91ae8aef9b3b73997b1f9cbcc7375.js:61)
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:59
at XMLHttpRequest.<anonymous> (5be91ae8aef9b3b73997b1f9cbcc7375.js:78)
at Object._RECEIVE_MESSAGE_TYPE.<computed> [as postrobot_message_response] (types.js:121)
at receiveMessage (index.js:114)
at messageListener (index.js:140)
_RECEIVE_MESSAGE_TYPE.<computed> @ types.js:121
receiveMessage @ index.js:114
messageListener @ index.js:140
setTimeout (async)
(anonymous) @ exceptions.js:16
(anonymous) @ promise.js:122
setTimeout (async)
_proto.reject @ promise.js:120
_loop @ promise.js:171
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
_loop @ promise.js:171
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
_loop @ promise.js:178
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
(anonymous) @ promise.js:207
_loop @ promise.js:176
_proto.dispatch @ promise.js:153
_proto.reject @ promise.js:127
(anonymous) @ promise.js:51
respond @ client.js:147
_RECEIVE_MESSAGE_TYPE.<computed> @ types.js:126
receiveMessage @ index.js:114
messageListener @ index.js:140
5be91ae8aef9b3b73997b1f9cbcc7375.js:350 Uncaught Error: Error: TypeError: Cannot read property 'messages' of undefined
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:625
at _loop (5be91ae8aef9b3b73997b1f9cbcc7375.js:62)
at ZalgoPromise._proto.dispatch (5be91ae8aef9b3b73997b1f9cbcc7375.js:63)
at ZalgoPromise._proto.resolve (5be91ae8aef9b3b73997b1f9cbcc7375.js:61)
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:59
at XMLHttpRequest.<anonymous> (5be91ae8aef9b3b73997b1f9cbcc7375.js:78)
at Object._RECEIVE_MESSAGE_TYPE.<computed> [as postrobot_message_response] (checkout.4.0.286.js:4277)
at receiveMessage (checkout.4.0.286.js:4308)
at messageListener (checkout.4.0.286.js:4327)
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:350
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:350
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at replaceObject (5be91ae8aef9b3b73997b1f9cbcc7375.js:335)
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at 5be91ae8aef9b3b73997b1f9cbcc7375.js:335
at replaceObject (5be91ae8aef9b3b73997b1f9cbcc7375.js:335)
Environment
- WordPress Version: 5.2.3
- WooCommerce Version: 3.7.0
- PayPal Express Checkout Plugin Version: 1.6.17
- Browser [e.g. chrome, safari] and Version: Chrome 76.0.3809.132
- Any other plugins installed
WooCommerce Cardstream: by Cardstream – 1.3 Capability Manager Enhanced: by PublishPress – 1.7.5 Classic Editor: by WordPress Contributors – 1.5 Codisto LINQ by Codisto: by Codisto – 1.3.32 WPBakery Page Builder: by Michael M - WPBakery.com – 6.0.5 Porto Theme - Functionality: by P-Themes – 1.4 Regenerate Thumbnails: by Alex Mills (Viper007Bond) – 3.1.1 Uber Login Logo: by UberWeb – 1.5.1 WooCommerce Admin: by WooCommerce – 0.19.0 WooCommerce Conversion Tracking: by Tareq Hasan – 2.0.4 WooCommerce PayPal Checkout Gateway: by WooCommerce – 1.6.17 WooCommerce Google Analytics Integration: by WooCommerce – 1.4.14 WooCommerce: by Automattic – 3.7.0 Yoast SEO: by Team Yoast – 12.1 WP Rocket: by WP Media – 3.3.7 YITH Google Product Feed for WooCommerce Premium: by YITH – 1.1.6 YITH WooCommerce Brands Add-on Premium: by YITH – 1.3.5 YITH WooCommerce Bulk Product Editing Premium: by YITH – 1.2.16 YITH WooCommerce Dynamic Pricing and Discounts Premium: by YITH – 1.5.4 YITH WooCommerce Order Tracking Premium: by YITH – 1.5.9 YITH WooCommerce PDF Invoice and Shipping List Premium: by YITH – 2.0.6 YITH WooCommerce Tab Manager Premium: by YITH – 1.2.16
Additional context WP Rocket has a cache lifespan of 10 hours, two less than the standard WP Nonce lifespan of 12 hours. Is this plugin doing something differently which is shortening the lifespan of the nonce? Still testing, but a cache with a lifespan of as short as two hours was showing this symptom. A reasonable expiry should be expected to allow caching to work.
The specific nonce check is failing at woocommerce-gateway-paypal-express-checkout/includes/class-wc-gateway-ppec-cart-handler.php line 138
2390954-zd
Same issue. @mikkamp mentioned that from his observations, it seems that the plugin would need to say that the cart fragments would expire earlier in the case where a nonce was renewed. That way it can get a new cart fragment once a nonce expires.
Follow up with user once bug has been resolved.
Same Problem here on different Shops. Installed is wp-rocket as caching plugin. Chache expire time is set to 4h. Sometimes it works. I mentioned if you put the product in the cart and than use the express button it always works.
The cart fragment is saved in localstorage in the browser and has an expiry time of 1 day. The cart fragment includes the full cart including the payment buttons with the nonce. So even if the caching plugin isn't returning a cached page, it still wouldn't get a refreshed cart fragment until either:
- the cart in localstorage is expired
- the cart contents is changed
Since by default a nonce will have a lifetime of 12 - 24 hours, there is a chance that expires before the cart fragment does. So in theory, even if you take WP Rocket out of the picture, you could still end up with a scenario where the cart fragment has an expired nonce.
I don't see any filter available to make the cart fragment expire any earlier. But another workaround would be to make the nonce valid for longer as is shown here: https://codex.wordpress.org/WordPress_Nonces#Modifying_the_nonce_lifetime
The snippet would need to be modified to return 2 * DAY_IN_SECONDS which will make the nonce valid for 24 - 48 hours.
I mentioned if you put the product in the cart and than use the express button it always works.
That would be expected, because changing the cart contents forces the cart fragment to be refreshed.
Still testing, but a cache with a lifespan of as short as two hours was showing this symptom.
In that scenario how long was it since the cart contents were changed?
We had the same problem with wp super cache. WP Super Cache supports dynamics content to replace Placeholders in cache with real content. So we hooked into the cacheactions and wp_print_footer_scripts to change the nonce values.
Enable late Init, create a folder wp-super-cache-plugins in your plugins folder and create a php file with the following content:
<?php
class Wpsc_PaypalExpress
{
/**
* @var Wpsc_PaypalExpress
*/
protected static $instance;
protected $localizedFooterScriptHandles = [
'wc-gateway-ppec-generate-cart' => [
'objectName' => 'wc_ppec_generate_cart_context',
'placeHolder' => 'PPEC_GENERATE_CART_NONCE_PLACEHOLDER',
'nonce' => '_wc_ppec_generate_cart_nonce',
],
'wc-gateway-ppec-smart-payment-buttons' => [
'objectName' => 'wc_ppec_context',
'placeHolder' => 'PPEC_SMART_PAYMENT_BUTTON_NONCE_PLACEHOLDER',
'nonce' => '_wc_ppec_start_checkout_nonce',
],
];
public static function instance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function __construct()
{
if (function_exists('add_cacheaction')) {
add_cacheaction('add_cacheaction', [$this, 'addCachifyActions']);
add_cacheaction('wpsc_cachedata_safety', static function() { return 1; });
add_cacheaction( 'wpsc_cachedata', [$this, 'replacePlaceholders']);
}
}
public function addCachifyActions()
{
add_action('wp_print_footer_scripts', [$this, 'cachifyFooterLocations'], -1);
}
public function cachifyFooterLocations()
{
global $wp_scripts;
if (!($wp_scripts instanceof \WP_Dependencies)) {
return;
}
foreach($this->localizedFooterScriptHandles as $handle => $settings) {
$this->replaceNonce($handle, $settings);
}
}
protected function replaceNonce($handle, array $settings)
{
global $wp_scripts;
if (!wp_script_is($handle)) {
return;
}
$dep = $wp_scripts->registered[$handle];
/* @var $dep \_WP_Dependency */
$data = isset($dep->extra['data']) ? $dep->extra['data'] : null;
//"var $object_name = " . wp_json_encode( $l10n ) . ';';
//$object_name = wc_ppec_generate_cart_context
$data = str_replace('var ' . $settings['objectName'] . ' = ', '', $data);
$data = rtrim($data, ';');
$data = json_decode($data, true);
$nonceKey = str_replace('_wc_ppec_', '', $settings['nonce']);
$data[$nonceKey] = $settings['placeHolder'];
$dep->extra['data'] = '';
wp_localize_script($handle, $settings['objectName'], $data);
}
public function replacePlaceholders($cachedata)
{
foreach($this->localizedFooterScriptHandles as $handle => $settings) {
$cachedata = str_replace($settings['placeHolder'], wp_create_nonce( $settings['nonce'] ), $cachedata);
}
return $cachedata;
}
}
Wpsc_PaypalExpress::instance();
`
@DalbertHab can you give a quick explanation of what your plugin does?
@unhammer If WP Super Cache caches a page the nonce is cached, too. My plugin replaces the nonce values of _wc_ppec_generate_cart_nonce and _wc_ppec_start_checkout_nonce with placeholders after the page is rendered and before it is saved to cache (Method addCachifyActions). When a cached page is served, wp super cached calls another part of the plugin that replaces the placeholders with newly generated nonce-values (Method replacePlaceholders).
thanks, will try :)
@DalbertHab do you have a solution if i use the wp_rocket cache plugin? or is there an other solution?
Anyone help me with the solution because I need to speed up my website. Wp-rocket with PayPal, PayPal can't pop up when someone purchase
@devonto how do you solve the problem
@ihabalfaqeh
To be honest, I switched plugins! https://yithemes.com/themes/plugins/yith-paypal-express-checkout-for-woocommerce/
Thank you @devonto I ll try it