| @@ -310,12 +310,9 @@ | ||
| 310 | 310 | |
| 311 | 311 | |
| 312 | 312 | // We want this special page to have a certain title and maybe special scripts and so on, |
| 313 | 313 | // this gets run in template redirect for these pages. |
| 314 | - add_action('woo_vipps_before_handling_special_page', function ($action) { | |
| 315 | - // Change title dynamically depending on action. LP 2026-09-02 | |
| 316 | - add_filter('the_title', [$this, 'vipps_special_page_endpoint_title'], 10, 2); | |
| 317 | - }); | |
| 314 | + add_action('woo_vipps_before_handling_special_page', array($this, 'pre_special_page_actions')); | |
| 318 | 315 | |
| 319 | 316 | // Add an admin interface for this page as well IOK 2026-09-11 |
| 320 | 317 | add_action('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages')); |
| 321 | 318 | |
| @@ -1046,12 +1043,30 @@ | ||
| 1046 | 1043 | |
| 1047 | 1044 | public function get_html_button_attrs_for_context($context = 'global') { |
| 1048 | 1045 | $options = get_option('vipps_button_options2', []); |
| 1049 | 1046 | if (!is_string($context)) $context = 'global'; |
| 1047 | + | |
| 1048 | + // Gutenberg express checkout buttons really want to be stretched, so we'll treat them somewhat differently. | |
| 1049 | + $gutenberg = false; | |
| 1050 | + if ($context == 'checkout_gutenberg') { | |
| 1051 | + $context = 'checkout'; | |
| 1052 | + $gutenberg = true; | |
| 1053 | + } | |
| 1054 | + if ($context == 'cart_gutenberg') { | |
| 1055 | + $context = 'cart'; | |
| 1056 | + $gutenberg = true; | |
| 1057 | + } | |
| 1058 | + | |
| 1050 | 1059 | $config = $options['express']['configs'][$context] ?? []; |
| 1051 | - if (!$config || ($config['use-global-config'] ?? false)) { | |
| 1060 | + $use_global = !$config || ($config['use-global-config'] ?? false); | |
| 1061 | + if ($use_global) { | |
| 1052 | 1062 | $config = $options['express']['configs']['global'] ?? $this->get_html_button_default_attrs(); |
| 1053 | 1063 | } |
| 1064 | + | |
| 1065 | + // see above. | |
| 1066 | + if ($gutenberg) { | |
| 1067 | + $config['stretched']='true'; | |
| 1068 | + } | |
| 1054 | 1069 | return $config; |
| 1055 | 1070 | } |
| 1056 | 1071 | |
| 1057 | 1072 | public function get_html_button_for_context($context = 'global') { |
| @@ -1138,8 +1153,10 @@ | ||
| 1138 | 1153 | private function button_menu_express_section() { |
| 1139 | 1154 | $options = get_option('vipps_button_options2', []); |
| 1140 | 1155 | $express = $options['express'] ?? []; |
| 1141 | 1156 | $configs = $express['configs'] ?? []; |
| 1157 | + | |
| 1158 | + | |
| 1142 | 1159 | $contexts = [ |
| 1143 | 1160 | 'global' => __('Global', 'woo-vipps'), |
| 1144 | 1161 | 'product' => __('Product', 'woo-vipps'), |
| 1145 | 1162 | 'catalog' => __('Catalog', 'woo-vipps'), |
| @@ -1312,8 +1329,9 @@ | ||
| 1312 | 1329 | |
| 1313 | 1330 | // Swap to new context: set all input fields to the stored values if exists. LP 2026-06-25 |
| 1314 | 1331 | const newContext = jQuery("#context").val(); |
| 1315 | 1332 | const newConfig = contextConfigs[newContext]; |
| 1333 | + | |
| 1316 | 1334 | setInputsFromConfig(newContext, newConfig); |
| 1317 | 1335 | currentContext = newContext; |
| 1318 | 1336 | } |
| 1319 | 1337 | |
| @@ -1914,16 +1932,15 @@ | ||
| 1914 | 1932 | public function minicart_express_checkout_button() { |
| 1915 | 1933 | $gw = $this->gateway(); |
| 1916 | 1934 | |
| 1917 | 1935 | if ($gw->show_express_checkout()){ |
| 1918 | - return $this->cart_express_checkout_button_html(true); | |
| 1936 | + return $this->cart_express_checkout_button_html('minicart'); | |
| 1919 | 1937 | } |
| 1920 | 1938 | } |
| 1921 | 1939 | |
| 1922 | - public function cart_express_checkout_button_html($minicart = false) { | |
| 1940 | + public function cart_express_checkout_button_html($context= 'cart') { | |
| 1923 | 1941 | $url = $this->express_checkout_url(); |
| 1924 | 1942 | $url = wp_nonce_url($url,'express','sec'); |
| 1925 | - $context = $minicart ? 'minicart' : 'cart'; | |
| 1926 | 1943 | $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context)); |
| 1927 | 1944 | $method = $this->get_payment_method_name(); |
| 1928 | 1945 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); |
| 1929 | 1946 | $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>"; |
| @@ -1966,9 +1983,9 @@ | ||
| 1966 | 1983 | public function express_checkout_button_shortcode() { |
| 1967 | 1984 | $gw = $this->gateway(); |
| 1968 | 1985 | if (!$gw->cart_supports_express_checkout()) return; |
| 1969 | 1986 | ob_start(); |
| 1970 | - $this->cart_express_checkout_button_html('shortcode'); | |
| 1987 | + $this->cart_express_checkout_button_html('cart'); | |
| 1971 | 1988 | return ob_get_clean(); |
| 1972 | 1989 | } |
| 1973 | 1990 | // Show a banner normally shown for non-logged-in-users at the checkout page. It does not need to check if we are to show the button, obviously, but needs to see if the cart works |
| 1974 | 1991 | public function express_checkout_banner_shortcode() { |
| @@ -2649,12 +2666,30 @@ | ||
| 2649 | 2666 | remove_filter('template_redirect', 'redirect_canonical', 10); |
| 2650 | 2667 | // dont cache special page. LP 2026-08-25 |
| 2651 | 2668 | $this->nocache(); |
| 2652 | 2669 | // Do the custom pre-load actions for these pages IOK 2026-09-11 |
| 2653 | - do_action('woo_vipps_before_handling_special_page', $_GET['action']); | |
| 2670 | + do_action('woo_vipps_before_handling_special_page', ($_GET['action'] ?? "")); | |
| 2654 | 2671 | } |
| 2655 | 2672 | } |
| 2656 | 2673 | |
| 2674 | + // Ran in template redirect for the special page. IOK 2026-09-2 | |
| 2675 | + public function pre_special_page_actions ($action) { | |
| 2676 | + // Change title dynamically depending on action. LP 2026-09-02 | |
| 2677 | + add_filter('the_title', [$this, 'vipps_special_page_endpoint_title'], 10, 2); | |
| 2678 | + | |
| 2679 | + // If we are handling the 'wait for payment' action, we need to poll the order status before | |
| 2680 | + // we start producing content IOK 2026-09-21 | |
| 2681 | + if ($action == 'wait_for_payment') { | |
| 2682 | + $this->handle_payment_poll_and_redirect(); | |
| 2683 | + } | |
| 2684 | + | |
| 2685 | + // Some validation is required for this action | |
| 2686 | + if ($action == 'do_express_checkout') { | |
| 2687 | + $this->vipps_express_checkout_consistency_check(); | |
| 2688 | + } | |
| 2689 | + } | |
| 2690 | + | |
| 2691 | + | |
| 2657 | 2692 | // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02 |
| 2658 | 2693 | public function vipps_special_page_endpoint_title($title, $postid = 0) { |
| 2659 | 2694 | global $wp_query; |
| 2660 | 2695 | // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02 |
| @@ -4133,9 +4168,9 @@ | ||
| 4133 | 4168 | WC()->cart->calculate_totals(); |
| 4134 | 4169 | WC()->cart->set_session(); |
| 4135 | 4170 | return true; |
| 4136 | 4171 | } catch (Exception $e) { |
| 4137 | - $this->log(sprintf(__("Error regenerating cart from order %1\$d: %2\$s", 'woo-vipps'), $order_id, $e->get_message()), 'error'); | |
| 4172 | + $this->log(sprintf(__("Error regenerating cart from order %1\$d: %2\$s", 'woo-vipps'), $order_id, $e->getMessage()), 'error'); | |
| 4138 | 4173 | return false; |
| 4139 | 4174 | } |
| 4140 | 4175 | } |
| 4141 | 4176 | |
| @@ -4245,9 +4280,11 @@ | ||
| 4245 | 4280 | if ( empty($_REQUEST['add-to-cart']) || ! is_numeric($_REQUEST['add-to-cart']) || empty($_REQUEST['vipps_compat_mode']) || !$_REQUEST['vipps_compat_mode']) { |
| 4246 | 4281 | return $url; |
| 4247 | 4282 | } |
| 4248 | 4283 | $url = $this->express_checkout_url(); |
| 4249 | - $url = wp_nonce_url($url,'express','sec'); | |
| 4284 | + // At this point, there is always a query argument here. IOK 2026-09-21 | |
| 4285 | + $nonce = wp_create_nonce('express'); | |
| 4286 | + $url = $url . "&sec=$nonce"; | |
| 4250 | 4287 | |
| 4251 | 4288 | return $url; |
| 4252 | 4289 | } |
| 4253 | 4290 | |
| @@ -4680,9 +4717,10 @@ | ||
| 4680 | 4717 | } |
| 4681 | 4718 | if (!$o) return; |
| 4682 | 4719 | if (!$o->get_meta('_vipps_single_product_express')) return; |
| 4683 | 4720 | if ($failed && !apply_filters('woo_vipps_restore_cart_on_express_checkout_failure', true, $o)) return; |
| 4684 | - if ($failed) WC()->cart->empty_cart(); | |
| 4721 | + // Restoring cart! But clear it first so we dont add this single product to the restored cart. LP 2026-09-22 | |
| 4722 | + WC()->cart->empty_cart(); | |
| 4685 | 4723 | $this->restore_cart($o); |
| 4686 | 4724 | } |
| 4687 | 4725 | |
| 4688 | 4726 | |
| @@ -5389,15 +5427,13 @@ | ||
| 5389 | 5427 | |
| 5390 | 5428 | return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args); |
| 5391 | 5429 | } |
| 5392 | 5430 | |
| 5393 | - // This is a landing page for the express checkout of then normal cart - it is done like this because this could take time on slower hosts. | |
| 5394 | - public function vipps_express_checkout() { | |
| 5431 | + public function vipps_express_checkout_consistency_check() { | |
| 5395 | 5432 | // We need a nonce to get here, but we should only get here when we have a cart, so this will not be cached. |
| 5396 | 5433 | // IOK 2018-05-28 |
| 5397 | 5434 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5398 | 5435 | |
| 5399 | - | |
| 5400 | 5436 | $backurl = wp_validate_redirect(@$_SERVER['HTTP_REFERER']); |
| 5401 | 5437 | if (!$backurl) $backurl = home_url(); |
| 5402 | 5438 | |
| 5403 | 5439 | if (!$ok) { |
| @@ -5411,8 +5447,19 @@ | ||
| 5411 | 5447 | wp_redirect($backurl); |
| 5412 | 5448 | exit(); |
| 5413 | 5449 | } |
| 5414 | 5450 | |
| 5451 | + add_filter('woo_vipps_express_checkout_consistent', '__return_true'); | |
| 5452 | + } | |
| 5453 | + | |
| 5454 | + // This is a landing page for the express checkout of then normal cart - it is done like this because this could take time on slower hosts. | |
| 5455 | + public function vipps_express_checkout() { | |
| 5456 | + // Some checks are made in template_redirect, we check here if they are ok IOK 2026-09-21 | |
| 5457 | + if (!apply_filters('woo_vipps_express_checkout_consistent', false)) { | |
| 5458 | + $content = __('Link expired, please try again', 'woo-vipps'); | |
| 5459 | + return $content; | |
| 5460 | + } | |
| 5461 | + | |
| 5415 | 5462 | add_filter('body_class', function ($classes) { |
| 5416 | 5463 | $classes[] = 'vipps-express-checkout'; |
| 5417 | 5464 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| 5418 | 5465 | return apply_filters('woo_vipps_express_checkout_body_class', $classes); |
| @@ -5616,12 +5663,11 @@ | ||
| 5616 | 5663 | } |
| 5617 | 5664 | } |
| 5618 | 5665 | |
| 5619 | 5666 | |
| 5620 | - | |
| 5621 | - public function vipps_wait_for_payment() { | |
| 5667 | + // Called in template_redirect before we get to the wait-for-payment page IOK 2026-09-21 | |
| 5668 | + private function handle_payment_poll_and_redirect () { | |
| 5622 | 5669 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5623 | - | |
| 5624 | 5670 | $order = null; |
| 5625 | 5671 | $gw = $this->gateway(); |
| 5626 | 5672 | |
| 5627 | 5673 | // Failsafe for when the session disappears IOK 2018-11-19 |
| @@ -5633,9 +5679,9 @@ | ||
| 5633 | 5679 | // If so, we will read the order id from the GET arguments and check if the auth token is correct, |
| 5634 | 5680 | // simulating the session with that. |
| 5635 | 5681 | // IOK 2019-11-19, changed to using GET 2023-01-23 |
| 5636 | 5682 | if ($no_session && $limited_session) { |
| 5637 | - $orderid = intval(@$_GET['id']); | |
| 5683 | + $orderid = intval($_GET['id'] ?? false); | |
| 5638 | 5684 | } |
| 5639 | 5685 | if ($orderid) { |
| 5640 | 5686 | clean_post_cache($orderid); |
| 5641 | 5687 | $order = wc_get_order($orderid); |
| @@ -5654,10 +5700,8 @@ | ||
| 5654 | 5700 | $session->set('_vipps_pending_order', $orderid); |
| 5655 | 5701 | } |
| 5656 | 5702 | } |
| 5657 | 5703 | |
| 5658 | - do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5659 | - | |
| 5660 | 5704 | $deleted_order=0; |
| 5661 | 5705 | if ($orderid && !$order) { |
| 5662 | 5706 | // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled. |
| 5663 | 5707 | // Concievably a hook on the 'cancel'-transition or in the callback handlers could clean that up before we get here. IOK 2019-09-26 |
| @@ -5682,9 +5726,9 @@ | ||
| 5682 | 5726 | clean_post_cache($orderid); |
| 5683 | 5727 | $order = wc_get_order($orderid); // Reload order object |
| 5684 | 5728 | } |
| 5685 | 5729 | } else { |
| 5686 | - // No need to do anyting here. IOK 2020-01-26 | |
| 5730 | + // No need to do anyting here. IOK 2020-01-26 | |
| 5687 | 5731 | } |
| 5688 | 5732 | |
| 5689 | 5733 | $payment = 'notchecked'; |
| 5690 | 5734 | if ($do_poll) { |
| @@ -5700,9 +5744,8 @@ | ||
| 5700 | 5744 | exit(); |
| 5701 | 5745 | } |
| 5702 | 5746 | |
| 5703 | 5747 | // We are done, but in failure. Don't poll. |
| 5704 | - $content = ""; | |
| 5705 | 5748 | $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); |
| 5706 | 5749 | |
| 5707 | 5750 | // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status. |
| 5708 | 5751 | // For failed it shows a "Retry payment" button that takes the customer to /pay-for-order where it will be retried. LP 2026-03-17 |
| @@ -5710,8 +5753,9 @@ | ||
| 5710 | 5753 | $failure_redirect = $failure_redirect ?: $gw->get_return_url($order); |
| 5711 | 5754 | wp_redirect($failure_redirect); |
| 5712 | 5755 | exit(); |
| 5713 | 5756 | } |
| 5757 | + | |
| 5714 | 5758 | if ($status == 'cancelled' || $payment == 'cancelled') { |
| 5715 | 5759 | $this->maybe_restore_cart($orderid,'failed'); |
| 5716 | 5760 | if ($failure_redirect){ |
| 5717 | 5761 | wp_redirect($failure_redirect); |
| @@ -5716,8 +5760,37 @@ | ||
| 5716 | 5760 | if ($failure_redirect){ |
| 5717 | 5761 | wp_redirect($failure_redirect); |
| 5718 | 5762 | exit(); |
| 5719 | 5763 | } |
| 5764 | + } else { | |
| 5765 | + // If not, enqueue the status checker IOK 2026-09-21 | |
| 5766 | + wp_enqueue_script('check-vipps',plugins_url('js/check-order-status.js',__FILE__),array('jquery','vipps-gw'),filemtime(dirname(__FILE__) . "/js/check-order-status.js"), 'true'); | |
| 5767 | + } | |
| 5768 | + | |
| 5769 | + // Communicate this to the shortcode IOK 2026-09-21 | |
| 5770 | + add_filter('woo_vipps_wait_for_payment_status', function () use($orderid, $status, $payment) { | |
| 5771 | + return ['orderid'=>$orderid, 'status'=>$status, 'payment'=>$payment]; | |
| 5772 | + }); | |
| 5773 | + | |
| 5774 | + } | |
| 5775 | + | |
| 5776 | + public function vipps_wait_for_payment() { | |
| 5777 | + | |
| 5778 | + // This will have been computed in template_redirect, but the status will be either still pending or failed. IOK 2026-09-21 | |
| 5779 | + $data = apply_filters('woo_vipps_wait_for_payment_status', []); | |
| 5780 | + | |
| 5781 | + $orderid = $data['orderid'] ?? 0; | |
| 5782 | + $status = $data['status'] ?? ""; | |
| 5783 | + $payment = $data['payment'] ?? ""; | |
| 5784 | + | |
| 5785 | + $order = wc_get_order($orderid); | |
| 5786 | + if (!$order) wp_die(__('Unknown order', 'woo-vipps')); | |
| 5787 | + | |
| 5788 | + do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5789 | + $gw = $this->gateway(); | |
| 5790 | + | |
| 5791 | + $content = ""; | |
| 5792 | + if ($status == 'cancelled' || $payment == 'cancelled') { | |
| 5720 | 5793 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5721 | 5794 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5722 | 5795 | $content .= "</div>"; |
| 5723 | 5796 | return $this->special_page_html('', $content); |
| @@ -5723,12 +5796,9 @@ | ||
| 5723 | 5796 | return $this->special_page_html('', $content); |
| 5724 | 5797 | } |
| 5725 | 5798 | |
| 5726 | 5799 | // Still pending and order is supposed to exist, so wait for Vipps. This happens all the time, so logging is removed. IOK 2018-09-27 |
| 5727 | - | |
| 5728 | 5800 | // Otherwise, go to a page waiting/polling for the callback. IOK 2018-05-16 |
| 5729 | - wp_enqueue_script('check-vipps',plugins_url('js/check-order-status.js',__FILE__),array('jquery','vipps-gw'),filemtime(dirname(__FILE__) . "/js/check-order-status.js"), 'true'); | |
| 5730 | - | |
| 5731 | 5801 | $signal = $this->callbackSignal($order); |
| 5732 | 5802 | $content = ""; |
| 5733 | 5803 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5734 | 5804 | |
| @@ -5736,15 +5806,16 @@ | ||
| 5736 | 5806 | $signalurl = $this->callbackSignalURL($signal); |
| 5737 | 5807 | |
| 5738 | 5808 | $content .= "</p></div>"; |
| 5739 | 5809 | |
| 5740 | - // We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24 | |
| 5741 | - $classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout"); | |
| 5742 | - $content .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>"; | |
| 5810 | + $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); | |
| 5811 | + | |
| 5812 | + // Carry the order status to the checking script IOK 2026-09-21 | |
| 5813 | + $content .= "<form id='vippsdata'>"; | |
| 5743 | 5814 | $content .= "<input type='hidden' id='fkey' name='fkey' value='".htmlspecialchars($signalurl)."'>"; |
| 5744 | 5815 | $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>"; |
| 5745 | 5816 | $content .= "<input type='hidden' name='action' value='check_order_status'>"; |
| 5746 | - $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5817 | + $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5747 | 5818 | $content .= "</form>"; |
| 5748 | 5819 | |
| 5749 | 5820 | |
| 5750 | 5821 | $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>'; |