| @@ -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 | |
| @@ -1840,8 +1837,10 @@ | ||
| 1840 | 1837 | } |
| 1841 | 1838 | |
| 1842 | 1839 | // Show express button option on checkout form. LP 2026-03-23 |
| 1843 | 1840 | public function checkout_before_customer_details_express () { |
| 1841 | + if (did_action('woo_vipps_checkout_before_customer_details_express')) return; | |
| 1842 | + do_action('woo_vipps_checkout_before_customer_details_express'); | |
| 1844 | 1843 | $gw = $this->gateway(); |
| 1845 | 1844 | if (!$gw->show_express_checkout()) return; |
| 1846 | 1845 | $this->express_checkout_section_html(); |
| 1847 | 1846 | } |
| @@ -2647,12 +2646,30 @@ | ||
| 2647 | 2646 | remove_filter('template_redirect', 'redirect_canonical', 10); |
| 2648 | 2647 | // dont cache special page. LP 2026-08-25 |
| 2649 | 2648 | $this->nocache(); |
| 2650 | 2649 | // Do the custom pre-load actions for these pages IOK 2026-09-11 |
| 2651 | - do_action('woo_vipps_before_handling_special_page', $_GET['action']); | |
| 2650 | + do_action('woo_vipps_before_handling_special_page', ($_GET['action'] ?? "")); | |
| 2652 | 2651 | } |
| 2653 | 2652 | } |
| 2654 | 2653 | |
| 2654 | + // Ran in template redirect for the special page. IOK 2026-09-2 | |
| 2655 | + public function pre_special_page_actions ($action) { | |
| 2656 | + // Change title dynamically depending on action. LP 2026-09-02 | |
| 2657 | + add_filter('the_title', [$this, 'vipps_special_page_endpoint_title'], 10, 2); | |
| 2658 | + | |
| 2659 | + // If we are handling the 'wait for payment' action, we need to poll the order status before | |
| 2660 | + // we start producing content IOK 2026-09-21 | |
| 2661 | + if ($action == 'wait_for_payment') { | |
| 2662 | + $this->handle_payment_poll_and_redirect(); | |
| 2663 | + } | |
| 2664 | + | |
| 2665 | + // Some validation is required for this action | |
| 2666 | + if ($action == 'do_express_checkout') { | |
| 2667 | + $this->vipps_express_checkout_consistency_check(); | |
| 2668 | + } | |
| 2669 | + } | |
| 2670 | + | |
| 2671 | + | |
| 2655 | 2672 | // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02 |
| 2656 | 2673 | public function vipps_special_page_endpoint_title($title, $postid = 0) { |
| 2657 | 2674 | global $wp_query; |
| 2658 | 2675 | // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02 |
| @@ -2795,9 +2812,9 @@ | ||
| 2795 | 2812 | add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button')); |
| 2796 | 2813 | add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30); |
| 2797 | 2814 | |
| 2798 | 2815 | // Previously we added an express html banner to the action 'woocommerce_before_checkout_form.', |
| 2799 | - // replaced by the new express buttons in manner more like Gutenberg. LP 2026-03-23 | |
| 2816 | + // replaced by the new express buttons in manner more like Gutenberg. for grepping: "express legacy checkout". LP 2026-03-23 | |
| 2800 | 2817 | add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5); |
| 2801 | 2818 | |
| 2802 | 2819 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2803 | 2820 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| @@ -4243,9 +4260,11 @@ | ||
| 4243 | 4260 | if ( empty($_REQUEST['add-to-cart']) || ! is_numeric($_REQUEST['add-to-cart']) || empty($_REQUEST['vipps_compat_mode']) || !$_REQUEST['vipps_compat_mode']) { |
| 4244 | 4261 | return $url; |
| 4245 | 4262 | } |
| 4246 | 4263 | $url = $this->express_checkout_url(); |
| 4247 | - $url = wp_nonce_url($url,'express','sec'); | |
| 4264 | + // At this point, there is always a query argument here. IOK 2026-09-21 | |
| 4265 | + $nonce = wp_create_nonce('express'); | |
| 4266 | + $url = $url . "&sec=$nonce"; | |
| 4248 | 4267 | |
| 4249 | 4268 | return $url; |
| 4250 | 4269 | } |
| 4251 | 4270 | |
| @@ -5299,10 +5318,12 @@ | ||
| 5299 | 5318 | // No point in expanding this unless we are actually doing the special actions. LP 2026-08-25 |
| 5300 | 5319 | if (is_admin()) return; |
| 5301 | 5320 | if (wp_doing_ajax()) return; |
| 5302 | 5321 | if (defined('REST_REQUEST') && REST_REQUEST) return; |
| 5322 | + if (did_filter('woo_vipps_special_page_html')) return; // User has somehow added two shortcodes. IOK 2026-09-18 | |
| 5303 | 5323 | |
| 5304 | 5324 | $action = $_GET['action'] ?? ''; |
| 5325 | + $html = ""; | |
| 5305 | 5326 | switch ($action) { |
| 5306 | 5327 | case 'wait_for_payment': |
| 5307 | 5328 | $html = $this->vipps_wait_for_payment(); |
| 5308 | 5329 | break; |
| @@ -5314,8 +5335,10 @@ | ||
| 5314 | 5335 | break; |
| 5315 | 5336 | default: |
| 5316 | 5337 | $html = ''; |
| 5317 | 5338 | } |
| 5339 | + // This is mostly to avoid this shortcode evaluating twice IOK 2026-09-18 | |
| 5340 | + $html = apply_filters('woo_vipps_special_page_html', $html, $action); | |
| 5318 | 5341 | |
| 5319 | 5342 | // Remember, this is a shortcode, so the html must be returned, not echoed IOK 2026-09-11 |
| 5320 | 5343 | return $html; |
| 5321 | 5344 | } |
| @@ -5324,8 +5347,9 @@ | ||
| 5324 | 5347 | // This URL will when accessed add a product to the cart and go directly to the express checkout page. |
| 5325 | 5348 | // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for |
| 5326 | 5349 | // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24 |
| 5327 | 5350 | public function vipps_buy_product() { |
| 5351 | + | |
| 5328 | 5352 | add_filter('body_class', function ($classes) { |
| 5329 | 5353 | $classes[] = 'vipps-express-checkout'; |
| 5330 | 5354 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| 5331 | 5355 | return apply_filters('woo_vipps_express_checkout_body_class', $classes); |
| @@ -5382,15 +5406,13 @@ | ||
| 5382 | 5406 | |
| 5383 | 5407 | return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args); |
| 5384 | 5408 | } |
| 5385 | 5409 | |
| 5386 | - // 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. | |
| 5387 | - public function vipps_express_checkout() { | |
| 5410 | + public function vipps_express_checkout_consistency_check() { | |
| 5388 | 5411 | // We need a nonce to get here, but we should only get here when we have a cart, so this will not be cached. |
| 5389 | 5412 | // IOK 2018-05-28 |
| 5390 | 5413 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5391 | 5414 | |
| 5392 | - | |
| 5393 | 5415 | $backurl = wp_validate_redirect(@$_SERVER['HTTP_REFERER']); |
| 5394 | 5416 | if (!$backurl) $backurl = home_url(); |
| 5395 | 5417 | |
| 5396 | 5418 | if (!$ok) { |
| @@ -5404,8 +5426,19 @@ | ||
| 5404 | 5426 | wp_redirect($backurl); |
| 5405 | 5427 | exit(); |
| 5406 | 5428 | } |
| 5407 | 5429 | |
| 5430 | + add_filter('woo_vipps_express_checkout_consistent', '__return_true'); | |
| 5431 | + } | |
| 5432 | + | |
| 5433 | + // 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. | |
| 5434 | + public function vipps_express_checkout() { | |
| 5435 | + // Some checks are made in template_redirect, we check here if they are ok IOK 2026-09-21 | |
| 5436 | + if (!apply_filters('woo_vipps_express_checkout_consistent', false)) { | |
| 5437 | + $content = __('Link expired, please try again', 'woo-vipps'); | |
| 5438 | + return $content; | |
| 5439 | + } | |
| 5440 | + | |
| 5408 | 5441 | add_filter('body_class', function ($classes) { |
| 5409 | 5442 | $classes[] = 'vipps-express-checkout'; |
| 5410 | 5443 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| 5411 | 5444 | return apply_filters('woo_vipps_express_checkout_body_class', $classes); |
| @@ -5609,12 +5642,11 @@ | ||
| 5609 | 5642 | } |
| 5610 | 5643 | } |
| 5611 | 5644 | |
| 5612 | 5645 | |
| 5613 | - | |
| 5614 | - public function vipps_wait_for_payment() { | |
| 5646 | + // Called in template_redirect before we get to the wait-for-payment page IOK 2026-09-21 | |
| 5647 | + private function handle_payment_poll_and_redirect () { | |
| 5615 | 5648 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5616 | - | |
| 5617 | 5649 | $order = null; |
| 5618 | 5650 | $gw = $this->gateway(); |
| 5619 | 5651 | |
| 5620 | 5652 | // Failsafe for when the session disappears IOK 2018-11-19 |
| @@ -5626,9 +5658,9 @@ | ||
| 5626 | 5658 | // If so, we will read the order id from the GET arguments and check if the auth token is correct, |
| 5627 | 5659 | // simulating the session with that. |
| 5628 | 5660 | // IOK 2019-11-19, changed to using GET 2023-01-23 |
| 5629 | 5661 | if ($no_session && $limited_session) { |
| 5630 | - $orderid = intval(@$_GET['id']); | |
| 5662 | + $orderid = intval($_GET['id'] ?? false); | |
| 5631 | 5663 | } |
| 5632 | 5664 | if ($orderid) { |
| 5633 | 5665 | clean_post_cache($orderid); |
| 5634 | 5666 | $order = wc_get_order($orderid); |
| @@ -5647,10 +5679,8 @@ | ||
| 5647 | 5679 | $session->set('_vipps_pending_order', $orderid); |
| 5648 | 5680 | } |
| 5649 | 5681 | } |
| 5650 | 5682 | |
| 5651 | - do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5652 | - | |
| 5653 | 5683 | $deleted_order=0; |
| 5654 | 5684 | if ($orderid && !$order) { |
| 5655 | 5685 | // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled. |
| 5656 | 5686 | // Concievably a hook on the 'cancel'-transition or in the callback handlers could clean that up before we get here. IOK 2019-09-26 |
| @@ -5675,9 +5705,9 @@ | ||
| 5675 | 5705 | clean_post_cache($orderid); |
| 5676 | 5706 | $order = wc_get_order($orderid); // Reload order object |
| 5677 | 5707 | } |
| 5678 | 5708 | } else { |
| 5679 | - // No need to do anyting here. IOK 2020-01-26 | |
| 5709 | + // No need to do anyting here. IOK 2020-01-26 | |
| 5680 | 5710 | } |
| 5681 | 5711 | |
| 5682 | 5712 | $payment = 'notchecked'; |
| 5683 | 5713 | if ($do_poll) { |
| @@ -5693,9 +5723,8 @@ | ||
| 5693 | 5723 | exit(); |
| 5694 | 5724 | } |
| 5695 | 5725 | |
| 5696 | 5726 | // We are done, but in failure. Don't poll. |
| 5697 | - $content = ""; | |
| 5698 | 5727 | $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); |
| 5699 | 5728 | |
| 5700 | 5729 | // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status. |
| 5701 | 5730 | // 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 |
| @@ -5703,8 +5732,9 @@ | ||
| 5703 | 5732 | $failure_redirect = $failure_redirect ?: $gw->get_return_url($order); |
| 5704 | 5733 | wp_redirect($failure_redirect); |
| 5705 | 5734 | exit(); |
| 5706 | 5735 | } |
| 5736 | + | |
| 5707 | 5737 | if ($status == 'cancelled' || $payment == 'cancelled') { |
| 5708 | 5738 | $this->maybe_restore_cart($orderid,'failed'); |
| 5709 | 5739 | if ($failure_redirect){ |
| 5710 | 5740 | wp_redirect($failure_redirect); |
| @@ -5709,8 +5739,37 @@ | ||
| 5709 | 5739 | if ($failure_redirect){ |
| 5710 | 5740 | wp_redirect($failure_redirect); |
| 5711 | 5741 | exit(); |
| 5712 | 5742 | } |
| 5743 | + } else { | |
| 5744 | + // If not, enqueue the status checker IOK 2026-09-21 | |
| 5745 | + 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'); | |
| 5746 | + } | |
| 5747 | + | |
| 5748 | + // Communicate this to the shortcode IOK 2026-09-21 | |
| 5749 | + add_filter('woo_vipps_wait_for_payment_status', function () use($orderid, $status, $payment) { | |
| 5750 | + return ['orderid'=>$orderid, 'status'=>$status, 'payment'=>$payment]; | |
| 5751 | + }); | |
| 5752 | + | |
| 5753 | + } | |
| 5754 | + | |
| 5755 | + public function vipps_wait_for_payment() { | |
| 5756 | + | |
| 5757 | + // This will have been computed in template_redirect, but the status will be either still pending or failed. IOK 2026-09-21 | |
| 5758 | + $data = apply_filters('woo_vipps_wait_for_payment_status', []); | |
| 5759 | + | |
| 5760 | + $orderid = $data['orderid'] ?? 0; | |
| 5761 | + $status = $data['status'] ?? ""; | |
| 5762 | + $payment = $data['payment'] ?? ""; | |
| 5763 | + | |
| 5764 | + $order = wc_get_order($orderid); | |
| 5765 | + if (!$order) wp_die(__('Unknown order', 'woo-vipps')); | |
| 5766 | + | |
| 5767 | + do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5768 | + $gw = $this->gateway(); | |
| 5769 | + | |
| 5770 | + $content = ""; | |
| 5771 | + if ($status == 'cancelled' || $payment == 'cancelled') { | |
| 5713 | 5772 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5714 | 5773 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5715 | 5774 | $content .= "</div>"; |
| 5716 | 5775 | return $this->special_page_html('', $content); |
| @@ -5716,12 +5775,9 @@ | ||
| 5716 | 5775 | return $this->special_page_html('', $content); |
| 5717 | 5776 | } |
| 5718 | 5777 | |
| 5719 | 5778 | // 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 |
| 5720 | - | |
| 5721 | 5779 | // Otherwise, go to a page waiting/polling for the callback. IOK 2018-05-16 |
| 5722 | - 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'); | |
| 5723 | - | |
| 5724 | 5780 | $signal = $this->callbackSignal($order); |
| 5725 | 5781 | $content = ""; |
| 5726 | 5782 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5727 | 5783 | |
| @@ -5729,15 +5785,16 @@ | ||
| 5729 | 5785 | $signalurl = $this->callbackSignalURL($signal); |
| 5730 | 5786 | |
| 5731 | 5787 | $content .= "</p></div>"; |
| 5732 | 5788 | |
| 5733 | - // We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24 | |
| 5734 | - $classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout"); | |
| 5735 | - $content .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>"; | |
| 5789 | + $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); | |
| 5790 | + | |
| 5791 | + // Carry the order status to the checking script IOK 2026-09-21 | |
| 5792 | + $content .= "<form id='vippsdata'>"; | |
| 5736 | 5793 | $content .= "<input type='hidden' id='fkey' name='fkey' value='".htmlspecialchars($signalurl)."'>"; |
| 5737 | 5794 | $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>"; |
| 5738 | 5795 | $content .= "<input type='hidden' name='action' value='check_order_status'>"; |
| 5739 | - $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5796 | + $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5740 | 5797 | $content .= "</form>"; |
| 5741 | 5798 | |
| 5742 | 5799 | |
| 5743 | 5800 | $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>'; |