PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.2.5
Pay with Vipps and MobilePay for WooCommerce v6.2.5
6.2.5 6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.10 6.1.9 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1.0 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 5.4.3 5.4.2 All 187 releases
← All changes | payment/Vipps.class.php +110 -32 6.2.0 → 6.2.5 View file →
@@ -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
@@ -1840,8 +1858,10 @@
1840 1858 }
1841 1859
1842 1860 // Show express button option on checkout form. LP 2026-03-23
1843 1861 public function checkout_before_customer_details_express () {
1862 + if (did_action('woo_vipps_checkout_before_customer_details_express')) return;
1863 + do_action('woo_vipps_checkout_before_customer_details_express');
1844 1864 $gw = $this->gateway();
1845 1865 if (!$gw->show_express_checkout()) return;
1846 1866 $this->express_checkout_section_html();
1847 1867 }
@@ -1912,16 +1932,15 @@
1912 1932 public function minicart_express_checkout_button() {
1913 1933 $gw = $this->gateway();
1914 1934
1915 1935 if ($gw->show_express_checkout()){
1916 - return $this->cart_express_checkout_button_html(true);
1936 + return $this->cart_express_checkout_button_html('minicart');
1917 1937 }
1918 1938 }
1919 1939
1920 - public function cart_express_checkout_button_html($minicart = false) {
1940 + public function cart_express_checkout_button_html($context= 'cart') {
1921 1941 $url = $this->express_checkout_url();
1922 1942 $url = wp_nonce_url($url,'express','sec');
1923 - $context = $minicart ? 'minicart' : 'cart';
1924 1943 $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context));
1925 1944 $method = $this->get_payment_method_name();
1926 1945 $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method);
1927 1946 $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>";
@@ -1964,9 +1983,9 @@
1964 1983 public function express_checkout_button_shortcode() {
1965 1984 $gw = $this->gateway();
1966 1985 if (!$gw->cart_supports_express_checkout()) return;
1967 1986 ob_start();
1968 - $this->cart_express_checkout_button_html('shortcode');
1987 + $this->cart_express_checkout_button_html('cart');
1969 1988 return ob_get_clean();
1970 1989 }
1971 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
1972 1991 public function express_checkout_banner_shortcode() {
@@ -2647,12 +2666,30 @@
2647 2666 remove_filter('template_redirect', 'redirect_canonical', 10);
2648 2667 // dont cache special page. LP 2026-08-25
2649 2668 $this->nocache();
2650 2669 // Do the custom pre-load actions for these pages IOK 2026-09-11
2651 - do_action('woo_vipps_before_handling_special_page', $_GET['action']);
2670 + do_action('woo_vipps_before_handling_special_page', ($_GET['action'] ?? ""));
2652 2671 }
2653 2672 }
2654 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 +
2655 2692 // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02
2656 2693 public function vipps_special_page_endpoint_title($title, $postid = 0) {
2657 2694 global $wp_query;
2658 2695 // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02
@@ -2795,9 +2832,9 @@
2795 2832 add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button'));
2796 2833 add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30);
2797 2834
2798 2835 // 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
2836 + // replaced by the new express buttons in manner more like Gutenberg. for grepping: "express legacy checkout". LP 2026-03-23
2800 2837 add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5);
2801 2838
2802 2839 add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button'));
2803 2840 add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20);
@@ -4131,9 +4168,9 @@
4131 4168 WC()->cart->calculate_totals();
4132 4169 WC()->cart->set_session();
4133 4170 return true;
4134 4171 } catch (Exception $e) {
4135 - $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');
4136 4173 return false;
4137 4174 }
4138 4175 }
4139 4176
@@ -4243,9 +4280,11 @@
4243 4280 if ( empty($_REQUEST['add-to-cart']) || ! is_numeric($_REQUEST['add-to-cart']) || empty($_REQUEST['vipps_compat_mode']) || !$_REQUEST['vipps_compat_mode']) {
4244 4281 return $url;
4245 4282 }
4246 4283 $url = $this->express_checkout_url();
4247 - $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";
4248 4287
4249 4288 return $url;
4250 4289 }
4251 4290
@@ -4678,9 +4717,10 @@
4678 4717 }
4679 4718 if (!$o) return;
4680 4719 if (!$o->get_meta('_vipps_single_product_express')) return;
4681 4720 if ($failed && !apply_filters('woo_vipps_restore_cart_on_express_checkout_failure', true, $o)) return;
4682 - 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();
4683 4723 $this->restore_cart($o);
4684 4724 }
4685 4725
4686 4726
@@ -5299,10 +5339,12 @@
5299 5339 // No point in expanding this unless we are actually doing the special actions. LP 2026-08-25
5300 5340 if (is_admin()) return;
5301 5341 if (wp_doing_ajax()) return;
5302 5342 if (defined('REST_REQUEST') && REST_REQUEST) return;
5343 + if (did_filter('woo_vipps_special_page_html')) return; // User has somehow added two shortcodes. IOK 2026-09-18
5303 5344
5304 5345 $action = $_GET['action'] ?? '';
5346 + $html = "";
5305 5347 switch ($action) {
5306 5348 case 'wait_for_payment':
5307 5349 $html = $this->vipps_wait_for_payment();
5308 5350 break;
@@ -5314,8 +5356,10 @@
5314 5356 break;
5315 5357 default:
5316 5358 $html = '';
5317 5359 }
5360 + // This is mostly to avoid this shortcode evaluating twice IOK 2026-09-18
5361 + $html = apply_filters('woo_vipps_special_page_html', $html, $action);
5318 5362
5319 5363 // Remember, this is a shortcode, so the html must be returned, not echoed IOK 2026-09-11
5320 5364 return $html;
5321 5365 }
@@ -5324,8 +5368,9 @@
5324 5368 // This URL will when accessed add a product to the cart and go directly to the express checkout page.
5325 5369 // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for
5326 5370 // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24
5327 5371 public function vipps_buy_product() {
5372 +
5328 5373 add_filter('body_class', function ($classes) {
5329 5374 $classes[] = 'vipps-express-checkout';
5330 5375 $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24
5331 5376 return apply_filters('woo_vipps_express_checkout_body_class', $classes);
@@ -5382,15 +5427,13 @@
5382 5427
5383 5428 return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args);
5384 5429 }
5385 5430
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() {
5431 + public function vipps_express_checkout_consistency_check() {
5388 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.
5389 5433 // IOK 2018-05-28
5390 5434 $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express');
5391 5435
5392 -
5393 5436 $backurl = wp_validate_redirect(@$_SERVER['HTTP_REFERER']);
5394 5437 if (!$backurl) $backurl = home_url();
5395 5438
5396 5439 if (!$ok) {
@@ -5404,8 +5447,19 @@
5404 5447 wp_redirect($backurl);
5405 5448 exit();
5406 5449 }
5407 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 +
5408 5462 add_filter('body_class', function ($classes) {
5409 5463 $classes[] = 'vipps-express-checkout';
5410 5464 $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24
5411 5465 return apply_filters('woo_vipps_express_checkout_body_class', $classes);
@@ -5609,12 +5663,11 @@
5609 5663 }
5610 5664 }
5611 5665
5612 5666
5613 -
5614 - 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 () {
5615 5669 $orderid = WC()->session->get('_vipps_pending_order');
5616 -
5617 5670 $order = null;
5618 5671 $gw = $this->gateway();
5619 5672
5620 5673 // Failsafe for when the session disappears IOK 2018-11-19
@@ -5626,9 +5679,9 @@
5626 5679 // If so, we will read the order id from the GET arguments and check if the auth token is correct,
5627 5680 // simulating the session with that.
5628 5681 // IOK 2019-11-19, changed to using GET 2023-01-23
5629 5682 if ($no_session && $limited_session) {
5630 - $orderid = intval(@$_GET['id']);
5683 + $orderid = intval($_GET['id'] ?? false);
5631 5684 }
5632 5685 if ($orderid) {
5633 5686 clean_post_cache($orderid);
5634 5687 $order = wc_get_order($orderid);
@@ -5647,10 +5700,8 @@
5647 5700 $session->set('_vipps_pending_order', $orderid);
5648 5701 }
5649 5702 }
5650 5703
5651 - do_action('woo_vipps_wait_for_payment_page',$order);
5652 -
5653 5704 $deleted_order=0;
5654 5705 if ($orderid && !$order) {
5655 5706 // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled.
5656 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
@@ -5675,9 +5726,9 @@
5675 5726 clean_post_cache($orderid);
5676 5727 $order = wc_get_order($orderid); // Reload order object
5677 5728 }
5678 5729 } else {
5679 - // No need to do anyting here. IOK 2020-01-26
5730 + // No need to do anyting here. IOK 2020-01-26
5680 5731 }
5681 5732
5682 5733 $payment = 'notchecked';
5683 5734 if ($do_poll) {
@@ -5693,9 +5744,8 @@
5693 5744 exit();
5694 5745 }
5695 5746
5696 5747 // We are done, but in failure. Don't poll.
5697 - $content = "";
5698 5748 $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid);
5699 5749
5700 5750 // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status.
5701 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
@@ -5703,8 +5753,9 @@
5703 5753 $failure_redirect = $failure_redirect ?: $gw->get_return_url($order);
5704 5754 wp_redirect($failure_redirect);
5705 5755 exit();
5706 5756 }
5757 +
5707 5758 if ($status == 'cancelled' || $payment == 'cancelled') {
5708 5759 $this->maybe_restore_cart($orderid,'failed');
5709 5760 if ($failure_redirect){
5710 5761 wp_redirect($failure_redirect);
@@ -5709,8 +5760,37 @@
5709 5760 if ($failure_redirect){
5710 5761 wp_redirect($failure_redirect);
5711 5762 exit();
5712 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') {
5713 5793 $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>';
5714 5794 $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>';
5715 5795 $content .= "</div>";
5716 5796 return $this->special_page_html('', $content);
@@ -5716,12 +5796,9 @@
5716 5796 return $this->special_page_html('', $content);
5717 5797 }
5718 5798
5719 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
5720 -
5721 5800 // 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 5801 $signal = $this->callbackSignal($order);
5725 5802 $content = "";
5726 5803 $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name());
5727 5804
@@ -5729,15 +5806,16 @@
5729 5806 $signalurl = $this->callbackSignalURL($signal);
5730 5807
5731 5808 $content .= "</p></div>";
5732 5809
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) . "'>";
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'>";
5736 5814 $content .= "<input type='hidden' id='fkey' name='fkey' value='".htmlspecialchars($signalurl)."'>";
5737 5815 $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>";
5738 5816 $content .= "<input type='hidden' name='action' value='check_order_status'>";
5739 - $content .= wp_nonce_field('vippsstatus','sec',1,false);
5817 + $content .= wp_nonce_field('vippsstatus','sec',1,false);
5740 5818 $content .= "</form>";
5741 5819
5742 5820
5743 5821 $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>';