PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.2.6
Pay with Vipps and MobilePay for WooCommerce v6.2.6
6.2.6 6.3.0 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 All 189 releases
← All changes | payment/Vipps.class.php +488 -490 6.3.0 → 6.2.6 View file →
@@ -117,10 +117,9 @@
117 117 add_action('wp_footer', array($Vipps,'footer'));
118 118 }
119 119 add_action( 'plugins_loaded', array($Vipps,'plugins_loaded'));
120 120 add_action( 'after_setup_theme', array($Vipps,'after_setup_theme'));
121 - add_action( 'init',array($Vipps,'init'));
122 - add_action( 'rest_api_init', array($Vipps, 'rest_api_init'));
121 + add_action('init',array($Vipps,'init'));
123 122 add_action( 'woocommerce_loaded', array($Vipps,'woocommerce_loaded'));
124 123 add_filter( 'woocommerce_available_payment_gateways', array($Vipps, 'payment_gateway_filter'));
125 124 add_action( 'woocommerce_blocks_loaded', [$Vipps, 'woocommerce_blocks_loaded']);
126 125 // Express Checkout and Checkout supports the new pickup_location shipping method, but the admin interface for this may
@@ -228,9 +227,8 @@
228 227 // Register certain scripts in wp_loaded because they will be added to the backend as well - the gutenberg checkout block
229 228 // needs these to be defined in the backend. IOK 2024-04-16
230 229 add_action('wp_loaded', array($this, 'wp_register_scripts'));
231 230 add_action('wp_enqueue_scripts', array($this, 'wp_enqueue_scripts'));
232 - add_action('wp_enqueue_scripts', array($this, 'enqueue_classic_checkout_scripts'), 20);
233 231
234 232 // Remove the possibility of restarting failed orders etc. This will be fixed in the future. IOK 2023-05-26
235 233 add_filter('woocommerce_my_account_my_orders_actions', array($this,'woocommerce_my_account_my_orders_actions'), 10, 2);
236 234
@@ -246,8 +244,16 @@
246 244
247 245 // Extra order actions on the order screen, now using ajax to be compatible with HPOS. IOK 2022-12-02
248 246 add_action('wp_ajax_woo_vipps_order_action', array($this, 'order_handle_vipps_action'));
249 247
248 + // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22
249 + add_action('rest_api_init', function() {
250 + register_rest_route(self::get_rest_namespace('v1'), '/express-products', [
251 + 'methods' => 'GET',
252 + 'callback' => [$this, 'rest_express_checkout_products'],
253 + 'permission_callback' => '__return_true',
254 + ]);
255 + });
250 256
251 257 // We need a 5-minute scheduled event for the handler for missed callbacks. Using the
252 258 // action scheduler would be better, but we can't do that just yet because of backwards
253 259 // compatibility. At some point, support for older woo-versions should be dropped; then this
@@ -308,32 +314,9 @@
308 314 add_action('woo_vipps_before_handling_special_page', array($this, 'pre_special_page_actions'));
309 315
310 316 // Add an admin interface for this page as well IOK 2026-09-11
311 317 add_action('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages'));
312 - }
313 318
314 -
315 - public function rest_api_init () {
316 -
317 - // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22
318 - register_rest_route(self::get_rest_namespace('v1'), '/express-products', [
319 - 'methods' => 'GET',
320 - 'callback' => [$this, 'rest_express_checkout_products'],
321 - 'permission_callback' => '__return_true',
322 - ]);
323 -
324 - // Start a single product express checkout process. IOK 2026-08-25
325 - register_rest_route(self::get_rest_namespace('v1'), '/express_checkout_single', [
326 - 'methods' => 'POST',
327 - 'callback' => [$this, 'rest_do_single_product_express_checkout'],
328 - 'permission_callback' => '__return_true',
329 - ]);
330 - // And one for the cart. IOK 2026-09-04
331 - register_rest_route(self::get_rest_namespace('v1'), '/express_checkout', [
332 - 'methods' => 'POST',
333 - 'callback' => [$this, 'rest_do_express_checkout'],
334 - 'permission_callback' => '__return_true',
335 - ]);
336 319 }
337 320
338 321 public function admin_init () {
339 322 $gw = $this->gateway();
@@ -362,8 +345,10 @@
362 345 // Styling etc
363 346 add_action('admin_head', array($this, 'admin_head'));
364 347
365 348 // Scripts
349 + $this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce');
350 + wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig);
366 351 add_action('admin_enqueue_scripts', array($this,'admin_enqueue_scripts'));
367 352
368 353 // IOK 2026-05-26 redirect the old Woo-generated settings-screen to our own settings page.
369 354 add_action('current_screen', function ($screen) {
@@ -1709,14 +1694,12 @@
1709 1694 <?php
1710 1695 }
1711 1696 // Scripts used in the backend
1712 1697 public function admin_enqueue_scripts($hook) {
1698 + // Add certain translations very late so translation plugins get a chance to work. IOK 2026-02-02
1699 + $this->script_add_vippslocale();
1713 1700
1714 - wp_register_script('vipps-admin',plugins_url('js/admin.js',__FILE__),array('jquery'),filemtime(dirname(__FILE__) . "/js/admin.js"), 'all');
1715 - $this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce');
1716 - wp_localize_script('vipps-admin', 'VippsConfig', $this->vippsJSConfig);
1717 - // Add certain translations very late so translation plugins get a chance to work. IOK 2026-02-02
1718 - $this->script_add_vippslocale('vipps-admin');
1701 + wp_register_script('vipps-admin',plugins_url('js/admin.js',__FILE__),array('jquery','vipps-gw'),filemtime(dirname(__FILE__) . "/js/admin.js"), 'all');
1719 1702 wp_enqueue_script('vipps-admin');
1720 1703
1721 1704 wp_enqueue_style('vipps-admin-style',plugins_url('css/admin.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/admin.css"), 'all');
1722 1705 wp_enqueue_style('vipps-fonts');
@@ -1786,9 +1769,12 @@
1786 1769
1787 1770 public function wp_register_scripts () {
1788 1771 // We are going to use the 'hooks' library introduced by WP 5.1, but we still support WP 4.7. So if this isn't enqueues
1789 1772 // (which it only is if Gutenberg is active) or not provided at all, add it now.
1790 - wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks', 'wp-api-fetch','vipps-widget-sdk'),filemtime(dirname(__FILE__) . "/js/vipps.js"), true);
1773 + if (!wp_script_is( 'wp-hooks', 'registered')) {
1774 + wp_register_script('wp-hooks', plugins_url('/compat/hooks.min.js', __FILE__));
1775 + }
1776 + wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/vipps.js"), 'true');
1791 1777
1792 1778 // Badges - web components provided by Vipps MobilePay to display payment options in-store.
1793 1779 wp_register_script('vipps-onsite-messageing',
1794 1780 plugins_url('js/vipps-on-site-messaging.js', WC_VIPPS_PAYMENT_MAIN_FILE),
@@ -1794,27 +1780,13 @@
1794 1780 plugins_url('js/vipps-on-site-messaging.js', WC_VIPPS_PAYMENT_MAIN_FILE),
1795 1781 array(),
1796 1782 filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-on-site-messaging.js'),
1797 1783 [
1798 - 'in_footer' => true,
1799 - 'strategy' => 'async',
1784 + 'in_footer' => true,
1785 + 'strategy' => 'async',
1800 1786 ],
1801 1787 );
1802 1788
1803 - add_filter( 'script_loader_tag', function($tag, $handle,$src) {
1804 - if ($handle == 'vipps-widget-sdk') {
1805 - $tag = preg_replace("!^<script!", "<script data-vipps-widget-sdk ", $tag);
1806 - return $tag;
1807 - }
1808 - return $tag;
1809 - },10,3);
1810 -
1811 - wp_register_script('vipps-widget-sdk', "https://cdn.vippsmobilepay.com/js/widget-sdk/vipps-widget.js",
1812 - array('vipps-button-webcomponent'),
1813 - filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps.js'),
1814 - ['in_footer' => true]
1815 - );
1816 -
1817 1789 // Button web component downloaded from https://cdn.vippsmobilepay.com/js/button/button.js. LP 2026-06-24
1818 1790 wp_register_script('vipps-button-webcomponent',
1819 1791 plugins_url('js/vipps-button.js', WC_VIPPS_PAYMENT_MAIN_FILE),
1820 1792 array(),
@@ -1820,45 +1792,27 @@
1820 1792 array(),
1821 1793 filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-button.js'),
1822 1794 [
1823 1795 'in_footer' => false
1824 - ]
1796 + ],
1825 1797 );
1826 1798 }
1827 1799
1828 1800 // Runs late in both wp_enqueue_scripts and admin_enqueue_scripts to make it more compatible with translation plugins IOK 2026-02-02
1829 - public function script_add_vippslocale ($handle) {
1801 + public function script_add_vippslocale () {
1830 1802 // This is actually for the payment block, where localize script has started to not-work in certain contexts. IOK 2022-12-13
1831 - $name = $this->get_payment_method_name();
1832 1803 $strings = array(
1833 - 'Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $name),
1834 - 'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $name),
1835 - 'pay_with_card' => sprintf(__('Pay with card through %1$s', 'woo-vipps'), $name),
1836 - 'termsAndConditionsError' => __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ),
1837 - 'temporaryError' => sprintf(__('%1$s is temporarily unavailable.','woo-vipps'),$name),
1838 - 'successMessage' => sprintf(__('To the %1$s app!','woo-vipps'), $name),
1839 - 'cancel'=> __("Cancel", 'woo-vipps'),
1840 - 'close'=> __("Close", 'woo-vipps'),
1841 - 'missingPaymentUrl'=> __("Successful checkout response has no payment URL", 'woo-vipps'),
1842 - 'expressCheckoutFailed'=> __("Express checkout failed", 'woo-vipps'),
1843 - 'unexpectedCheckoutResponse'=> __("Unexpected express checkout response", 'woo-vipps'),
1844 - 'vippsCheckoutFailed'=> __("Vipps Mobilepay checkout failed", 'woo-vipps'),
1845 - 'correctHighlightedFields'=> __("Please correct the highlighted fields.", 'woo-vipps'),
1846 - 'checkFormBeforeContinuing'=> __("Please check the form before continuing.", 'woo-vipps'),
1847 - 'cartCheckoutUnavailable'=> __("Cannot start express checkout: cart checkout is unavailable", 'woo-vipps'),
1848 - 'productIdentifiersMissing'=> __("Cannot buy product: product id, variation id and sku are missing", 'woo-vipps'),
1849 - 'productFormNotFound'=> __("Cannot buy product: product form not found", 'woo-vipps'),
1850 - 'paymentSuccessfulRedirecting' => __("Payment successful. Redirecting…", 'woo-vipps'),
1804 + 'Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $this->get_payment_method_name()),
1805 + 'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $this->get_payment_method_name()),
1806 + 'pay_with_card' => sprintf(__('Pay with card through %1$s', 'woo-vipps'), $this->get_payment_method_name()),
1851 1807 );
1852 - wp_localize_script($handle, 'VippsLocale', $strings);
1808 + wp_localize_script('vipps-gw', 'VippsLocale', $strings);
1853 1809 }
1854 1810
1855 1811 public function wp_enqueue_scripts() {
1856 - // Add late: if this value isn't 'yes' we wil not add order attribution to express orders. IOK 2026-09-10
1857 - $this->vippsJSConfig['expressOrderAttribution'] = $this->gateway()->get_option('vippsorderattribution');
1858 1812 wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig);
1859 1813 // Add certain translations very late so translation plugins get a chance to work. IOK 2026-02-02
1860 - $this->script_add_vippslocale('vipps-gw');
1814 + $this->script_add_vippslocale();
1861 1815
1862 1816 wp_enqueue_script('vipps-gw');
1863 1817 wp_enqueue_style('vipps-gw',plugins_url('css/vipps.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/vipps.css"));
1864 1818 wp_enqueue_script('vipps-button-webcomponent');
@@ -1863,55 +1817,13 @@
1863 1817 wp_enqueue_style('vipps-gw',plugins_url('css/vipps.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/vipps.css"));
1864 1818 wp_enqueue_script('vipps-button-webcomponent');
1865 1819 }
1866 1820
1867 - // These scripts should be loaded only on the checkout screen and is used only for the classic shortcode checkout and
1868 - // the pay-for-order screen. IOK 2026-09-15
1869 - public function enqueue_classic_checkout_scripts () {
1870 - if ( ! function_exists( 'is_checkout' ) || ! is_checkout() || is_order_received_page() ) {
1871 - return;
1872 - }
1873 - // Order-pay is rendered by the classic form even with a Blocks checkout page.
1874 - // It must bypass the check for the parent checkout page's block content.
1875 - if ( ! is_checkout_pay_page() ) {
1876 - $utils = '\\Automattic\\WooCommerce\\Blocks\\Utils\\CartCheckoutUtils';
1877 - $uses_checkout_block = is_callable( array( $utils, 'is_checkout_block_default' ) )
1878 - ? $utils::is_checkout_block_default()
1879 - : has_block( 'woocommerce/checkout', wc_get_page_id( 'checkout' ) );
1880 1821
1881 - if ( $uses_checkout_block ) {
1882 - return;
1883 - }
1884 - }
1885 -
1886 - // This script uses jQuery because the classic checkout screen does too. IOK 2026-09-15
1887 - $relative_path = 'js/vipps-classic-checkout.js';
1888 - wp_enqueue_script(
1889 - 'vipps-classic-checkout',
1890 - plugins_url( $relative_path, __FILE__ ),
1891 - array( 'jquery', 'wc-checkout', 'vipps-gw' ),
1892 - filemtime( plugin_dir_path( __FILE__ ) . $relative_path ),
1893 - true
1894 - );
1895 -
1896 - if ( is_checkout_pay_page() ) {
1897 - $order = wc_get_order( absint( get_query_var( 'order-pay' ) ) );
1898 - wp_add_inline_script( 'vipps-classic-checkout', 'window.VippsOrderPayConfig = ' . wp_json_encode( array(
1899 - 'orderId' => $order ? $order->get_id() : 0,
1900 - 'orderKey' => $order ? $order->get_order_key() : '',
1901 - 'billingEmail' => $order ? $order->get_billing_email() : '',
1902 - 'endpoint' => $order ? rest_url( 'wc/store/v1/checkout/' . $order->get_id() ) : '',
1903 - 'nonce' => wp_create_nonce( 'wc_store_api' ),
1904 - 'billingAddress' => $order ? $order->get_address( 'billing' ) : array(),
1905 - 'shippingAddress' => $order ? $order->get_address( 'shipping' ) : array(),
1906 - ) ) . ';', 'before' );
1907 - }
1908 - }
1909 -
1910 -
1911 1822 public function add_shortcodes() {
1912 1823 add_shortcode('woo_vipps_buy_now', array($this, 'buy_now_button_shortcode'));
1913 1824 add_shortcode('woo_vipps_express_checkout_button', array($this, 'express_checkout_button_shortcode'));
1825 + add_shortcode('woo_vipps_express_checkout_banner', array($this, 'express_checkout_banner_shortcode'));
1914 1826
1915 1827 // Badges, if using shortcodes
1916 1828 // New vipps-mobilepay-badge shortcode. LP 19.11.2024
1917 1829 add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode'));
@@ -1963,8 +1875,52 @@
1963 1875 $this->checkout_express_checkout_button_html();
1964 1876 echo '</fieldset>';
1965 1877 }
1966 1878
1879 + public function express_checkout_banner() {
1880 + $gw = $this->gateway();
1881 + if (!$gw->show_express_checkout()) return;
1882 + return $this->express_checkout_banner_html();
1883 + }
1884 +
1885 + public function express_checkout_banner_html() {
1886 + $url = $this->express_checkout_url();
1887 + $url = wp_nonce_url($url,'express','sec');
1888 + $text = __('Skip entering your address and just checkout using', 'woo-vipps');
1889 + $linktext = 'Express'; // dont translate. LP 2025-09-03
1890 + $logo = $this->get_express_banner_logo();
1891 + $payment_method = $this->get_payment_method_name();
1892 +
1893 + $img_classes = 'express-banner-logo inline negative ' . strtolower($payment_method) . '-logo';
1894 + $div_classes = 'woocommerce-info ' . strtolower($payment_method) . '-info';
1895 + $a_classes = 'express-banner-link ' . strtolower($payment_method) . '-link';
1896 +
1897 + $message = $text . "<a href='$url' class='$a_classes'><img class='$img_classes' border=0 src='$logo' alt='$payment_method'/>$linktext!</a>";
1898 + $message = apply_filters('woo_vipps_express_checkout_banner', $message, $url, $payment_method);
1899 + ?>
1900 + <div class="<?php echo $div_classes;?>"><?php echo $message;?></div>
1901 + <?php
1902 + }
1903 +
1904 + public function checkout_express_checkout_button() {
1905 + $gw = $this->gateway();
1906 +
1907 + if ($gw->show_express_checkout()){
1908 + return $this->checkout_express_checkout_button_html();
1909 + }
1910 + }
1911 +
1912 + public function checkout_express_checkout_button_html() {
1913 + $url = $this->express_checkout_url();
1914 + $url = wp_nonce_url($url,'express','sec');
1915 + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context('checkout'));
1916 + $method = $this->get_payment_method_name();
1917 + $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method);
1918 + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>";
1919 + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url);
1920 + echo $html;
1921 + }
1922 +
1967 1923 // Show the express button if reasonable to do so
1968 1924 public function cart_express_checkout_button() {
1969 1925 $gw = $this->gateway();
1970 1926
@@ -1980,38 +1936,19 @@
1980 1936 return $this->cart_express_checkout_button_html('minicart');
1981 1937 }
1982 1938 }
1983 1939
1984 - // This is for the Vipps SDK button used instead of the normal "pay for order" and "confirm order" buttons
1985 - // on the classic checkout and pay-for-order pages. It gets swapped in when the user selects vipps, and swapped out otherwise.
1986 - public function add_checkout_button_for_classic () {
1987 - $button = $this->get_html_button_for_context('checkout');
1988 - $submit = "<div class='vipps-classic-checkout-container'><button id='vipps-classic-checkout-submit' class='hidden vipps-submit-wrapper' type='submit'>$button</button></div>";
1989 - echo $submit;
1990 - }
1991 -
1992 1940 public function cart_express_checkout_button_html($context= 'cart') {
1993 - $button = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context));
1941 + $url = $this->express_checkout_url();
1942 + $url = wp_nonce_url($url,'express','sec');
1943 + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context));
1994 1944 $method = $this->get_payment_method_name();
1995 1945 $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method);
1996 - $url = "#";
1997 - $sec = wp_create_nonce('express');
1998 - $html = "<a href='#' class='vipps-express-checkout short " . esc_attr($method) . "' title='" . esc_attr($title) . "' data-sec='" . esc_attr($sec) . "'>$button</a>";
1946 + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>";
1999 1947 $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url);
2000 1948 echo $html;
2001 1949 }
2002 1950
2003 - public function checkout_express_checkout_button_html() {
2004 - $button = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context('checkout'));
2005 - $method = $this->get_payment_method_name();
2006 - $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method);
2007 - $url = "#";
2008 - $sec = wp_create_nonce('express');
2009 - $html = "<a href='#' class='vipps-express-checkout short " . esc_attr($method) . "' title='" . esc_attr($title) . "' data-sec='" . esc_attr($sec) . "'>$button</a>";
2010 - $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url);
2011 - echo $html;
2012 - }
2013 -
2014 1951 // A shortcode for a single buy now button. Express checkout must be active; but I don't check for this here, as this button may be
2015 1952 // cached. Therefore stock, purchasability etc will be done later. IOK 2018-10-02
2016 1953 public function buy_now_button_shortcode ($atts) {
2017 1954 // The new web component button args. LP 2026-07-02
@@ -2049,8 +1986,16 @@
2049 1986 ob_start();
2050 1987 $this->cart_express_checkout_button_html('cart');
2051 1988 return ob_get_clean();
2052 1989 }
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
1991 + public function express_checkout_banner_shortcode() {
1992 + $gw = $this->gateway();
1993 + if (!$gw->cart_supports_express_checkout()) return;
1994 + ob_start();
1995 + $this->express_checkout_banner_html();
1996 + return ob_get_clean();
1997 + }
2053 1998
2054 1999 // Manage the various product meta fields
2055 2000 public function process_product_meta ($id, $post) {
2056 2001 // This is for the 'buy now' button
@@ -2740,17 +2685,11 @@
2740 2685 // Some validation is required for this action
2741 2686 if ($action == 'do_express_checkout') {
2742 2687 $this->vipps_express_checkout_consistency_check();
2743 2688 }
2744 - // These two actions require an extra script
2745 - if (in_array($action, ['buy_product','do_express_checkout'])) {
2746 - wp_enqueue_script('vipps-purchase', plugins_url('js/vipps-purchase.js',__FILE__), ['vipps-gw'],
2747 - filemtime(dirname(__FILE__) . "/js/vipps-purchase.js"),
2748 - ['in_footer'=>true]
2749 - );
2750 - }
2751 2689 }
2752 2690
2691 +
2753 2692 // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02
2754 2693 public function vipps_special_page_endpoint_title($title, $postid = 0) {
2755 2694 global $wp_query;
2756 2695 // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02
@@ -2899,12 +2838,8 @@
2899 2838
2900 2839 add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button'));
2901 2840 add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20);
2902 2841
2903 - // For the classic checkout page and pay-for-order page, use a custom submit button when payment method
2904 - // is Vipps
2905 - add_action('woocommerce_review_order_after_submit', array($this, 'add_checkout_button_for_classic'));
2906 - add_action('woocommerce_pay_order_after_submit', array($this, 'add_checkout_button_for_classic'));
2907 2842
2908 2843 // Special pages and callbacks handled by template_redirect. IOK 2023-02-22
2909 2844 add_action('template_redirect', array($this,'template_redirect'),1);
2910 2845
@@ -2914,8 +2849,21 @@
2914 2849 // Ajax endpoints for checking the order status while waiting for confirmation
2915 2850 add_action('wp_ajax_nopriv_check_order_status', array($this, 'ajax_check_order_status'));
2916 2851 add_action('wp_ajax_check_order_status', array($this, 'ajax_check_order_status'));
2917 2852
2853 +
2854 + // Buying a single product directly using express checkout IOK 2018-09-28
2855 + add_action('wp_ajax_nopriv_vipps_buy_single_product', array($this, 'ajax_vipps_buy_single_product'));
2856 + add_action('wp_ajax_vipps_buy_single_product', array($this, 'ajax_vipps_buy_single_product'));
2857 +
2858 + // This is for express checkout which we will also do asynchronously IOK 2018-05-28
2859 + add_action('wp_ajax_nopriv_do_express_checkout', array($this, 'ajax_do_express_checkout'));
2860 + add_action('wp_ajax_do_express_checkout', array($this, 'ajax_do_express_checkout'));
2861 +
2862 + // Same thing, but for single products IOK 2018-05-28
2863 + add_action('wp_ajax_nopriv_do_single_product_express_checkout', array($this, 'ajax_do_single_product_express_checkout'));
2864 + add_action('wp_ajax_do_single_product_express_checkout', array($this, 'ajax_do_single_product_express_checkout'));
2865 +
2918 2866 // Handle the cancel unpaid order action when the "hold stock" times out.
2919 2867 // For *normal* vipps orders, we run another cronjob every 5. minute which checks order status,
2920 2868 // therefore here it suffices to check if the order is 'cancelled' at Vipps, and if so we return.
2921 2869 // For Checkout the rules are different though.
@@ -2994,8 +2942,9 @@
2994 2942 $this->vippsJSConfig = array();
2995 2943 $this->vippsJSConfig['vippsajaxurl'] = admin_url('admin-ajax.php');
2996 2944 $this->vippsJSConfig['BuyNowWith'] = __('Buy now with', 'woo-vipps');
2997 2945 $this->vippsJSConfig['BuyNowWithVipps'] = sprintf(__('Buy now with %1$s', 'woo-vipps'), $this->get_payment_method_name());
2946 + $this->vippsJSConfig['vippslogourl'] = plugins_url('img/vipps_logo_negativ_rgb_transparent.png',__FILE__);
2998 2947 $this->vippsJSConfig['vippssmileurl'] = plugins_url('img/vmp-logo.png',__FILE__);
2999 2948 $this->vippsJSConfig['vippsbuynowbutton'] = sprintf(__( '%1$s Buy Now button', 'woo-vipps' ), $this->get_payment_method_name());
3000 2949 $this->vippsJSConfig['vippsbuynowdescription'] = sprintf(__( 'Add a %1$s Buy Now-button to the product block or choose a product manually', 'woo-vipps'), $this->get_payment_method_name());
3001 2950 $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language();
@@ -3000,10 +2949,8 @@
3000 2949 $this->vippsJSConfig['vippsbuynowdescription'] = sprintf(__( 'Add a %1$s Buy Now-button to the product block or choose a product manually', 'woo-vipps'), $this->get_payment_method_name());
3001 2950 $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language();
3002 2951 $this->vippsJSConfig['vippslocale'] = get_locale();
3003 2952 $this->vippsJSConfig['vippsexpressbuttonurl'] = $this->get_payment_method_name();
3004 - $this->vippsJSConfig['paymentMethodSlug'] = sanitize_title($this->get_payment_method_name());
3005 - $this->vippsJSConfig['paymentMethodName'] = $this->get_payment_method_name();
3006 2953
3007 2954
3008 2955 // If the site supports Gutenberg Blocks, support the Checkout block IOK 2020-08-10
3009 2956 if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) {
@@ -4534,12 +4481,28 @@
4534 4481 $order = wc_get_order($order->get_id());
4535 4482 $order_status = $order->get_status();
4536 4483
4537 4484 if ($order_status != 'pending') return $order_status;
4538 -
4539 - $gw = $this->gateway();
4485 + // No callback has occured yet. If this has been going on for a while, check directly with Vipps
4486 + // We can't use the vipps init timestamp here, because that may be in the past for Checkout at least. IOK 2025-08-13
4487 + if ($order_status == 'pending') {
4488 + if (WC()->session) {
4489 + $now = time();
4490 + $then = WC()->session->get('_vipps_check_' . $order->get_id());
4491 + if (!$then) {
4492 + $then = $now;
4493 + WC()->session->set('_vipps_check_' . $order->get_id(), $then);
4494 + }
4495 + if (($then + (1 * 30)) > $now) { // more than half a minute? Start checking at Vipps
4496 + return $order_status;
4497 + }
4498 + } else {
4499 + // No session shouldn't be possible, but if it is..
4500 + return $order_status;
4501 + }
4502 + }
4540 4503 $this->log("Checking order status on Vipps for order id: " . $order->get_id(), 'info');
4541 - $newstatus = $gw->poll_and_check_order_status($order);
4504 + return $this->check_status_of_pending_order($order);
4542 4505 }
4543 4506
4544 4507 // In some situations we have to empty the cart when the user goes to Vipps, so
4545 4508 // we store it in the session and restore it if the users cancels. IOK 2018-05-07
@@ -4607,9 +4570,8 @@
4607 4570
4608 4571 // Maybe log in user
4609 4572 // It is done on the thank-you page of the order, and only for express checkout.
4610 4573 function maybe_log_in_user ($order) {
4611 -
4612 4574 if (is_user_logged_in()) return;
4613 4575 if (!$order || ! self::is_vipps_order($order)) return;
4614 4576
4615 4577 // We *do* want to log in express checkout customers, but not those that
@@ -4761,227 +4723,124 @@
4761 4723 $this->restore_cart($o);
4762 4724 }
4763 4725
4764 4726
4765 - // Actually create a express checkout order object, with no shipping or personal information, returning information about
4766 - // the result. The order should at this point be in a/the cart. For single product purchases, this is a different cart than
4767 - // the main one; for cart purchases, it's just the WC()->cart object. IOK 2026-08-25
4768 - private function create_and_process_express_order() {
4769 - $result = null;
4770 - $gw = $this->gateway();
4771 - try {
4772 - $orderid = $gw->create_partial_order();
4773 - do_action('woo_vipps_ajax_do_express_checkout', $orderid);
4774 - } catch (Exception $e) {
4775 - $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false);
4776 - return $result;
4777 - }
4778 - if (!$orderid) {
4779 - $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4780 - return $result;
4727 + public function ajax_vipps_buy_single_product () {
4728 + Vipps::nocache();
4729 + static::set_locale_if_in_header();
4730 + // We're not checking ajax referer here, because what we do is creating a session and redirecting to the
4731 + // 'create order' page wherein we'll do the actual work. IOK 2018-09-28
4732 + $session = WC()->session;
4733 + if (!$session->has_session()) {
4734 + $session->set_customer_session_cookie(true);
4781 4735 }
4736 + $session->set('__vipps_buy_product', json_encode($_REQUEST));
4782 4737
4783 - try {
4784 - $this->maybe_add_static_shipping($gw,$orderid);
4785 - } catch (Exception $e) {
4786 - $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error');
4787 - $this->log($e->getMessage(),'error');
4788 - $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4789 - return $result;
4790 - }
4738 + // Incredibly, some caches will cache this page even with cookies set and no-cache headers set. So we try to
4739 + // add yet another way to inform caches that this is, in fact, not cacheable. IOK 2023-06-12
4740 + $url = add_query_arg('nc', sha1(uniqid(WC()->session->get_customer_id(),true)), $this->buy_product_url());
4791 4741
4792 - // Now pass this to the Woo gateway and get a redirect URL back IOK 2026-08-25
4793 - $ok = $gw->process_payment($orderid);
4794 - if ($ok && $ok['result'] == 'success') {
4795 - $result = array('ok'=>1, 'orderid'=>$orderid, 'msg'=>'', 'url'=>$ok['redirect']);
4796 - return $result;
4797 - }
4798 - $result = array('ok'=>0, 'orderid'=>$orderid, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>'');
4799 - return $result;
4742 + $result = array('ok'=>1, 'msg'=>__('Processing order... ','woo-vipps'), 'url'=> $url);
4743 + wp_send_json($result);
4744 + exit();
4800 4745 }
4801 4746
4802 - // This creates a simple hash for the 'current order' which we will store in the session if we proceed to checkout. We use this to
4803 - // avoid/warn the user of duplicate purchases. IOK 2026-09-09
4804 - public function create_order_hash($args=null) {
4805 - // If we have no arguments, we'll hash the cart.
4806 - if (empty($args)) {
4807 - $cartitems = WC()->cart->get_cart();
4808 - $orderspec = array();
4809 - foreach($cartitems as $item => $values) {
4810 - $orderspec[] = array('sku'=> ($values['sku'] ?? ""), 'product_id'=>($values['product_id'] ?? 0), 'variation_id'=>($values['variation_id'] ?? 0), 'quantity'=>($values['quantity'] ?? 1));
4811 - }
4812 - $args = $orderspec;
4813 - }
4814 - return md5(serialize($args));
4815 - }
4816 -
4817 -
4818 - // This method may provide HTML form elements to ask a user questions after starting
4819 - // express checkout. It is used to detect duplicate orders, possibly for terms and conditions, and user-definiable customizations. IOK 2026-09-09
4820 - // NULL productinfo means use the cart; the "current hash" is used to detect duplicates, and is calculated by the caller.
4821 - public function express_order_needs_confirmation($args, $productinfo, $current_hash) {
4822 - $elements = [];
4823 - $html = "";
4824 -
4825 - // First, let's check if we need to confirm the purchase.
4826 - $last_express_purchase_hash = WC()->session->get('woo_vipps_last_express');
4827 - if ($last_express_purchase_hash) {
4828 - list($hash, $orderid, $stamp) = explode(":", $last_express_purchase_hash);
4829 - $cutoff = $stamp + apply_filters('woo_vipps_recent_order_cutoff', (3*60));
4830 - if ($hash == $current_hash && (time() <= $cutoff )) {
4831 - $order = wc_get_order($orderid);
4832 - $status = $order ? $order->get_status() : false;
4833 - // IOK TODO/FIXME actually, if the order is pending/failed/cancelled and *identical* to our current productinfo, we could plausibly do a restart here. Would probably require careful checking though, and
4834 - // a different flow. IOK 2026-09-17
4835 - if (in_array($status, ['on-hold', 'processing', 'completed'])) {
4836 - $header = __("Are you sure?",'woo-vipps');
4837 - $body = __("You recently completed an order with exactly the same products as you are buying now. There should be an email in your inbox from the previous purchase. Are you sure you want to order again?",'woo-vipps');
4838 - $elements['possible_duplicate'] = "<h1>$header</h1><p>$body</p>";
4839 - $this->log(__("It seems a customer is trying to re-order product(s) recently bought in the same session, asking user for confirmation", 'woo-vipps'), 'info');
4840 - }
4841 - }
4842 - }
4843 -
4747 + public function ajax_do_express_checkout () {
4748 + check_ajax_referer('do_express','sec');
4749 + Vipps::nocache();
4750 + static::set_locale_if_in_header();
4844 4751 $gw = $this->gateway();
4845 - $askForTerms = function_exists('wc_terms_and_conditions_checkbox_enabled') ? wc_terms_and_conditions_checkbox_enabled() : true;
4846 - $askForTerms = $askForTerms && ($gw->get_option('expresscheckout_termscheckbox') == 'yes');
4847 - $askForTerms = apply_filters('woo_vipps_express_checkout_terms_and_conditions_checkbox_enabled', $askForTerms);
4848 4752
4849 - if ($askForTerms) {
4850 - $termsHTML = '';
4851 - // Include shop terms
4852 - ob_start();
4853 - wc_get_template('checkout/terms.php');
4854 - $termsHTML = ob_get_clean();
4855 - $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML);
4856 - $elements['terms'] = $termsHTML;
4753 + if (!$gw->express_checkout_available() || !$gw->cart_supports_express_checkout()) {
4754 + $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false);
4755 + wp_send_json($result);
4756 + exit();
4857 4757 }
4858 4758
4859 - // Custom fields
4860 - ob_start();
4861 - do_action('woo_vipps_express_checkout_orderspec_form', $productinfo, $args);
4862 - $extra_fields = ob_get_clean();
4863 - if (!empty($extra_fields)) {
4864 - $elements['extra'] = $extra_fields;
4865 - }
4866 4759
4867 - if (!empty($elements)) {
4868 - $html = join("\n", array_values($elements));
4869 - $msg = join(",", array_keys($elements));
4870 - return ['ok'=>2, 'msg'=>$msg, 'html'=>$html, 'url'=>''];
4871 - }
4760 +
4872 4761
4873 - return false;
4874 -
4875 - }
4876 -
4877 - public function rest_do_express_checkout ($request) {
4878 - Vipps::nocache();
4879 - check_ajax_referer('express', 'sec');
4880 - static::set_locale_if_in_header();
4881 - $args = $request->get_json_params();
4882 - if (!$args) {
4883 - return new WP_Error('no_data', __('No data passed to express checkout', 'woo-vipps'), ['status' => 400]);
4884 - }
4885 -
4886 - // Since this is the REST api, we need to load the cart manually here. IOK 2026-08-27
4887 - if ( is_null( WC()->cart ) ) {
4888 - WC()->frontend_includes();
4889 - if ( ! WC()->session instanceof WC_Session ) {
4890 - WC()->session = new WC_Session_Handler();
4891 - WC()->session->init();
4892 - }
4893 - if (is_null( WC()->customer)) {
4894 - WC()->customer = new WC_Customer( get_current_user_id(), true );
4895 - }
4896 - WC()->cart = new WC_Cart();
4897 - WC()->cart->get_cart_from_session();
4898 - }
4899 -
4900 -
4901 - $gw = $this->gateway();
4902 - if (!$gw->express_checkout_available() || !$gw->cart_supports_express_checkout()) {
4903 - $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false);
4904 - return $result;
4905 - }
4906 4762 // Validate cart going forward using same logic as WC_Cart->check_cart() but not adding notices.
4907 4763 $toolate = false;
4908 4764 $msg = "";
4909 4765 $valid = WC()->cart->check_cart_item_validity();
4910 4766 if ( is_wp_error( $valid) ) {
4911 - $toolate = true;
4912 - $msg = "<br>" . $valid->get_error_message();
4767 + $toolate = true;
4768 + $msg = "<br>" . $valid->get_error_message();
4913 4769 }
4914 4770 $stock = WC()->cart->check_cart_item_stock();
4915 - if ( is_wp_error( $stock) ) {
4916 - $toolate = true;
4917 - $msg = "<br>" . $stock->get_error_message();
4918 - }
4771 + if ( is_wp_error( $stock) ) {
4772 + $toolate = true;
4773 + $msg = "<br>" . $stock->get_error_message();
4774 + }
4919 4775
4920 4776 if ($toolate) {
4921 4777 $result = array('ok'=>0, 'msg'=>sprintf(__('Some of the products in your cart are no longer available in the quantities you have ordered. Please <a href="%1$s">edit your order</a> before continuing the checkout','woo-vipps'), wc_get_cart_url()) . $msg, 'url'=>false);
4922 - return $result;
4778 + wp_send_json($result);
4779 + exit();
4923 4780 }
4924 4781
4925 - // Then the cookies. These would be the _ga and sbjs_ cookies typically, but we'll let users handle these themselves.
4926 - // These are passed as arguments from the javascript, since proxies are likely to strip them. This should allow
4927 - // systems like MonsterInsights that look for the _GA cookie to succeed. IOK 2026-08-30
4928 - $cookies = $args['cookies'] ?? [];
4929 - foreach($cookies as $key => $value) {
4930 - if (!isset($_COOKIE[$key])) {
4931 - $_COOKIE[$key] = $value;
4932 - }
4782 + try {
4783 + $orderid = $gw->create_partial_order();
4784 + do_action('woo_vipps_ajax_do_express_checkout', $orderid);
4785 + } catch (Exception $e) {
4786 + $this->log($e->getMessage(),'error');
4787 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false);
4788 + wp_send_json($result);
4789 + exit();
4790 + }
4791 + if (!$orderid) {
4792 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4793 + wp_send_json($result);
4794 + exit();
4933 4795 }
4934 - // There might be extra values here now, which would typically have been posted as POST arguments, in a form.
4935 - // User-defined stuff and so on. We'll initiate the POST value with these to simulate this for backwards compatibility.
4936 - $others =$args['post'] ?? [];
4937 - foreach($args['post'] as $key=>$value) {
4938 - $_POST[$key] = $value;
4939 - }
4940 4796
4941 - // Try to avoid re-purchasing the same order repeatedly. IOK 2026-09-02
4942 - $current_hash = $this->create_order_hash();
4943 - $confirmation = (bool) intval(($others['confirmed'] ?? 0));
4944 - if (!$confirmation) {
4945 - $result = $this->express_order_needs_confirmation($args, null, $current_hash);
4946 - if (!empty($result)) {
4947 - return $result;
4948 - }
4797 + try {
4798 + $this->maybe_add_static_shipping($gw,$orderid);
4799 + } catch (Exception $e) {
4800 + $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error');
4801 + $this->log($e->getMessage(),'error');
4802 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4803 + wp_send_json($result);
4804 + exit();
4949 4805 }
4950 -
4951 - $result = $this->create_and_process_express_order();
4952 - if ($result['ok'] == 1) {
4953 - $orderid = $result['orderid'];
4954 - WC()->session->set('woo_vipps_last_express', "$current_hash:$orderid:" . time());
4955 - WC()->session->save_data();
4806 +
4807 + $ok = $gw->process_payment($orderid);
4808 + if ($ok && $ok['result'] == 'success') {
4809 + $result = array('ok'=>1, 'msg'=>'', 'url'=>$ok['redirect']);
4810 + wp_send_json($result);
4811 + exit();
4956 4812 }
4957 - return $result;
4958 -
4813 + $result = array('ok'=>0, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>'');
4814 + wp_send_json($result);
4815 + exit();
4959 4816 }
4960 4817
4818 + // Same as ajax_do_express_checkout, but for a single product/variation. Duplicate code because we want to manipulate the cart differently here. IOK 2018-09-25
4819 + public function ajax_do_single_product_express_checkout() {
4820 + check_ajax_referer('do_express','sec');
4821 + Vipps::nocache();
4822 + static::set_locale_if_in_header();
4823 + require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php");
4824 + $gw = $this->gateway();
4961 4825
4962 - // Rest handler for single product express checkout. Expects arguments as JSON. IOK 2026-08-25
4963 - public function rest_do_single_product_express_checkout ($request) {
4964 - Vipps::nocache();
4965 - static::set_locale_if_in_header();
4966 - $args = $request->get_json_params();
4967 - if (!$args) {
4968 - return new WP_Error('no_data', __('No data passed to express checkout', 'woo-vipps'), ['status' => 400]);
4826 + if (!$gw->express_checkout_available()) {
4827 + $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false);
4828 + wp_send_json($result);
4829 + exit();
4969 4830 }
4970 - $result = ['ok' => 0, 'msg'=>'', 'orderid'=>0, 'url'=>''];
4971 4831
4972 - // We receive the varid, prodid, sku and quantity directly. One of these. The sku is the dominant one. IOK 2026-08-27
4973 - $varid = intval($args['variation_id'] ?? 0);
4974 - $prodid = intval($args['product_id'] ?? 0);
4975 - $sku = sanitize_text_field($args['sku'] ?? "");
4976 - $quantity = max(1, intval($args['quantity'] ?? 0));
4977 4832
4833 + // Here we will either have a product-id, a variant-id and a product-id, or just a SKU. The product-id will not be a variant - but
4834 + // we'll double-check just in case. Also if we somehow *just* get a variant-id we should fix that too. But a SKU trumps all. IOK 2018-10-02
4835 + $varid = intval(@$_POST['variation_id']);
4836 + $prodid = intval(@$_POST['product_id']);
4837 + $sku = sanitize_text_field(@$_POST['sku']);
4838 + $quant = intval(@$_POST['quantity']);
4978 4839
4979 - // We expect the variations - that is, the fields named "attribute_..." to be sent as post fields.
4980 - // We just need to sanitize them.
4981 - $variations = [];
4982 - $invars = $args['post'] ?? [];
4983 - foreach ($invars as $key => $value) {
4840 + // Get any attributes posted for variable products (where one of the dimensions is "any" for instance)
4841 + $variations = array();
4842 + foreach ($_POST as $key => $value ) {
4984 4843 if ( 'attribute_' !== substr( $key, 0, 10 ) ) {
4985 4844 continue;
4986 4845 }
4987 4846 $variations[ sanitize_title( wp_unslash( $key ) ) ] = wp_unslash( $value );
@@ -4986,94 +4845,15 @@
4986 4845 }
4987 4846 $variations[ sanitize_title( wp_unslash( $key ) ) ] = wp_unslash( $value );
4988 4847 }
4989 4848
4990 - // Then the cookies. These would be the _ga and sbjs_ cookies typically, but we'll let users handle these themselves.
4991 - // These are passed as arguments from the javascript, since proxies are likely to strip them. This should allow
4992 - // systems like MonsterInsights that look for the _GA cookie to succeed. IOK 2026-08-30
4993 - $cookies = $args['cookies'] ?? [];
4994 - foreach($cookies as $key => $value) {
4995 - if (!isset($_COOKIE[$key])) {
4996 - $_COOKIE[$key] = $value;
4997 - }
4998 - }
4849 + $product = null;
4850 + $variant = null;
4851 + $parent = null;
4852 + $parentid = null;
4853 + $quantity = 1;
4854 + if ($quant && $quant>1) $quantity=$quant;
4999 4855
5000 - // There might be extra values here now, which would typically have been posted as POST arguments, in a form.
5001 - // User-defined stuff and so on. We'll initiate the POST value with these to simulate this for backwards compatibility.
5002 - $others =$args['post'] ?? [];
5003 - foreach($args['post'] as $key=>$value) {
5004 - $_POST[$key] = $value;
5005 - }
5006 -
5007 - // Since this is the REST api, we need to load the cart manually here. *Not* loading the cart could be an option but unpredictable. IOK 2026-08-27
5008 - if ( is_null( WC()->cart ) ) {
5009 - WC()->frontend_includes();
5010 - if ( ! WC()->session instanceof WC_Session ) {
5011 - WC()->session = new WC_Session_Handler();
5012 - WC()->session->init();
5013 -
5014 - // If we don't have a session cookie, we need to set it, and also initialize the $_COOKIE value. IOK 2026-09-29
5015 - if (! WC()->session->get_session_cookie()) {
5016 - $store_session_cookie = function ( $options, $name, $value ) { $_COOKIE[$name] = $value; return $options;};
5017 - add_filter('woocommerce_set_cookie_options', $store_session_cookie, 10, 3);
5018 - try {
5019 - WC()->session->set_customer_session_cookie( true ); // We have to explicitly set the cookie if this session is fresh. IOK 2026-09-29
5020 - } finally {
5021 - remove_filter('woocommerce_set_cookie_options', $store_session_cookie, 10);
5022 - }
5023 - }
5024 - }
5025 - if (is_null( WC()->customer)) {
5026 - WC()->customer = new WC_Customer( get_current_user_id(), true );
5027 - }
5028 - WC()->cart = new WC_Cart();
5029 - WC()->cart->get_cart_from_session();
5030 - }
5031 -
5032 - // Try to avoid re-purchasing the same order repeatedly. IOK 2026-09-02
5033 - // We calculate this here so we can add it to the session later. IOK 2026-09-09
5034 - $orderspec = array('sku'=> $sku, 'product_id'=>$prodid, 'variation_id'=>$varid, 'quantity'=>$quantity);
5035 - $current_hash = $this->create_order_hash($orderspec);
5036 -
5037 - // Now to handle "extra questions" for an order, including terms + conditions and "possible duplicate order" IOK 2026-09-09
5038 - $confirmation = (bool) intval(($others['confirmed'] ?? 0));
5039 - if (!$confirmation) {
5040 - $result = $this->express_order_needs_confirmation($args, $orderspec, $current_hash);
5041 - if (!empty($result)) {
5042 - $response = new WP_REST_Response($result);
5043 - $response->set_status(200);
5044 - return $response;
5045 - }
5046 - }
5047 -
5048 - // Basically always return 200 after this, and always return an object with an 'ok' and a 'msg' value, possibly 'orderid' and 'url'.
5049 - $result = $this->really_do_single_product_express_checkout($prodid, $varid, $sku, $quantity, $variations);
5050 - // And if we're going to express now so let's note the order. IOK 2026-08-27. Now this assumes success, but *basically* I think this is ok.
5051 - // We'll reset it on order failure I think. IOK 2026-08-20 FIXME
5052 - if ($result['ok'] == 1) {
5053 - $orderid = $result['orderid'];
5054 - WC()->session->set('woo_vipps_last_express', "$current_hash:$orderid:" . time());
5055 - WC()->session->save_data();
5056 - }
5057 -
5058 - $response = new WP_REST_Response($result);
5059 - $response->set_status(200);
5060 -
5061 - return $response;
5062 - }
5063 -
5064 - // Common private method to do single product express checkout, used by the new REST express. IOK 2026-08-25
5065 - private function really_do_single_product_express_checkout($prodid, $varid, $sku, $quantity=1, $variations=[]) {
5066 - require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php");
5067 - $gw = $this->gateway();
5068 -
5069 - if (!$gw->express_checkout_available()) {
5070 - $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false);
5071 - return $result;
5072 - }
5073 - // Here we will either have a product-id, a variant-id and a product-id, or just a SKU. The product-id will not be a variant - but
5074 - // we'll double-check just in case. Also if we somehow *just* get a variant-id we should fix that too. But a SKU trumps all. IOK 2018-10-02
5075 -
5076 4856 // Find the product, or variation, and get everything in order so we can check existence, availability etc. IOK 2018-10-02
5077 4857 // Moved rules around as the _sku variant broke in 3.6.1 for stores that didn't bother to update the database IOK 2019-04-24
5078 4858 // This broke single-product purchases for variable products; fixed IOK 2019-05-21 thanks to Gaute Terland Nilsen @ Easyweb for the report
5079 4859 try {
@@ -5086,14 +4866,17 @@
5086 4866 $product = wc_get_product($skuid);
5087 4867 }
5088 4868 } catch (Exception $e) {
5089 4869 $result = array('ok'=>0, 'msg'=>__('Error finding product - cannot create order','woo-vipps'), 'url'=>false);
5090 - return $result;
4870 + wp_send_json($result);
4871 + exit();
5091 4872 }
5092 4873
4874 +
5093 4875 if (!$product) {
5094 4876 $result = array('ok'=>0, 'msg'=>__('Unknown product, cannot create order','woo-vipps'), 'url'=>false);
5095 - return $result;
4877 + wp_send_json($result);
4878 + exit();
5096 4879 }
5097 4880
5098 4881 $parentid = $product ? $product->get_parent_id() : null; // If the product is a variation, then the parent product is the parentid.
5099 4882 $parent = $parentid ? wc_get_product($parentid) : null;
@@ -5100,30 +4883,34 @@
5100 4883
5101 4884 // This can't really happen, but if it did..
5102 4885 if ($prodid && $parentid && ($prodid != $parentid)) {
5103 4886 $result = array('ok'=>0, 'msg'=>__('Selected product variant is not available','woo-vipps'), 'url'=>false);
5104 - return $result;
4887 + wp_send_json($result);
4888 + exit();
5105 4889 }
5106 4890 if (!$gw->product_supports_express_checkout($product)) {
5107 4891 $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false);
5108 - return $result;
4892 + wp_send_json($result);
4893 + exit();
5109 4894 }
5110 4895
5111 4896 // Somebody addded the wrong SKU
5112 4897 if ($product->get_type() == 'variable'){
5113 4898 $result = array('ok'=>0, 'msg'=>__('Selected product variant is not available for purchase','woo-vipps'), 'url'=>false);
5114 - return $result;
4899 + wp_send_json($result);
4900 + exit();
5115 4901 }
5116 4902 // Final check of availability
5117 4903 if (!$product->is_purchasable() || !$product->is_in_stock()) {
5118 4904 $result = array('ok'=>0, 'msg'=>__('Your product is temporarily no longer available for purchase','woo-vipps'), 'url'=>false);
5119 - return $result;
4905 + wp_send_json($result);
4906 + exit();
5120 4907 }
5121 4908
5122 4909 // Now it should be safe to continue to the checkout process. IOK 2018-10-02
4910 +
5123 4911 // Create a new temporary cart for this order. We need to get (and save) the real session cart,
5124 4912 // because some plugins actually override this.
5125 - // NB: Please note the cart must have been loaded here, be aware when doing REST. IOK 2026-08-27
5126 4913 $current_cart = clone WC()->cart;
5127 4914 WC()->cart->empty_cart();
5128 4915
5129 4916 if ($parent && $parent->get_type() == 'variable') {
@@ -5130,22 +4917,50 @@
5130 4917 WC()->cart->add_to_cart($parent->get_id(),$quantity,$product->get_id(), $variations);
5131 4918 } else {
5132 4919 WC()->cart->add_to_cart($product->get_id(),$quantity);
5133 4920 }
5134 - WC()->session->save_data();
5135 4921
5136 - $result = $this->create_and_process_express_order();
4922 + try {
4923 + $orderid = $gw->create_partial_order();
4924 + do_action('woo_vipps_ajax_do_express_checkout', $orderid);
4925 + } catch (Exception $e) {
4926 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false);
4927 + wp_send_json($result);
4928 + exit();
4929 + }
5137 4930
5138 - if ($result['ok'] ?? false) {
5139 - // Single product purchase, so save any contents of the real cart
5140 - $orderid = $result['orderid'];
5141 - $order = wc_get_order($orderid);
5142 - $order->update_meta_data('_vipps_single_product_express',true);
5143 - $order->save();
5144 - $this->save_cart($order,$current_cart);
4931 + if (!$orderid) {
4932 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4933 + wp_send_json($result);
4934 + exit();
5145 4935 }
5146 4936
5147 - return $result;
4937 + try {
4938 + $this->maybe_add_static_shipping($gw,$orderid);
4939 + } catch (Exception $e) {
4940 + $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error');
4941 + $this->log($e->getMessage(),'error');
4942 + $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false);
4943 + wp_send_json($result);
4944 + exit();
4945 + }
4946 +
4947 +
4948 + // Single product purchase, so save any contents of the real cart
4949 + $order = wc_get_order($orderid);
4950 + $order->update_meta_data('_vipps_single_product_express',true);
4951 + $order->save();
4952 + $this->save_cart($order,$current_cart);
4953 +
4954 + $ok = $gw->process_payment($orderid);
4955 + if ($ok && $ok['result'] == 'success') {
4956 + $result = array('ok'=>1, 'msg'=>'', 'url'=>$ok['redirect']);
4957 + wp_send_json($result);
4958 + exit();
4959 + }
4960 + $result = array('ok'=>0, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>'');
4961 + wp_send_json($result);
4962 + exit();
5148 4963 }
5149 4964
5150 4965 // This calculates and adds static shipping info to a partial order for express checkout if merchant has enabled this. IOK 2020-03-19
5151 4966 // Made visible for consistency with add_static_shipping. IOK 2021-10-22
@@ -5211,9 +5026,9 @@
5211 5026 $transaction = sanitize_text_field(@$_POST['transaction']);
5212 5027
5213 5028 $sessionorders= WC()->session->get('_vipps_session_orders');
5214 5029 if (!isset($sessionorders[$orderid])) {
5215 - wp_send_json(array('status'=>'error', 'msg'=>__('Not a session order','woo-vipps')));
5030 + wp_send_json(array('status'=>'error', 'msg'=>__('Not an order','woo-vipps')));
5216 5031 }
5217 5032
5218 5033 $order = wc_get_order($orderid);
5219 5034 if (!$order) {
@@ -5470,8 +5285,9 @@
5470 5285 echo "<div class='vipps_buy_now_wrapper loop'>$button</div>";
5471 5286 }
5472 5287
5473 5288
5289 +
5474 5290 // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist
5475 5291 // IOK 2026-04-30 remove this when checkout is end-of-life'd
5476 5292 // We now also use this for the vipps special page, previously a fakepage. LP 2026-08-18
5477 5293 public function woocommerce_create_pages ($data) {
@@ -5552,8 +5368,15 @@
5552 5368 // This URL will when accessed add a product to the cart and go directly to the express checkout page.
5553 5369 // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for
5554 5370 // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24
5555 5371 public function vipps_buy_product() {
5372 +
5373 + add_filter('body_class', function ($classes) {
5374 + $classes[] = 'vipps-express-checkout';
5375 + $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24
5376 + return apply_filters('woo_vipps_express_checkout_body_class', $classes);
5377 + });
5378 +
5556 5379 do_action('woo_vipps_express_checkout_page');
5557 5380
5558 5381 $session = WC()->session;
5559 5382 $posted = $session->get('__vipps_buy_product');
@@ -5587,26 +5410,23 @@
5587 5410 }
5588 5411
5589 5412 // Pass the productinfo to the express checkout form
5590 5413 $args = array();
5591 - $args['product_id'] = esc_attr(intval($productinfo['product_id'] ?? 0));
5592 - $args['variation_id'] = esc_attr(intval($productinfo['variation_id'] ?? 0));
5593 - $args['sku'] = esc_attr(sanitize_text_field($productinfo['product_sku'] ?? ""));
5594 - $args['quantity'] = esc_attr(max(1, intval($productinfo['quantity'] ?? 0)));
5414 + $args['quantity'] = 1;
5415 + if (array_key_exists('product_id',$productinfo)) $args['product_id'] = intval($productinfo['product_id']);
5416 + if (array_key_exists('variation_id',$productinfo)) $args['variation_id'] = intval($productinfo['variation_id']);
5417 + if (array_key_exists('product_sku',$productinfo)) $args['sku'] = sanitize_text_field($productinfo['product_sku']);
5418 + if (array_key_exists('quantity',$productinfo)) $args['quantity'] = intval($productinfo['quantity']);
5595 5419
5596 - $payment_method = $this->get_payment_method_name();
5597 - $btitle = esc_attr(sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method));
5598 - $bclass = esc_attr($payment_method);
5420 + // For variable products where some of the attributes are "any", we need to add these as well. This is from woos form-handler for these.
5421 + foreach ($productinfo as $key => $value) {
5422 + if ( 'attribute_' !== substr( $key, 0, 10 ) ) {
5423 + continue;
5424 + }
5425 + $args[sanitize_title(wp_unslash($key))] = sanitize_text_field(wp_unslash($value));
5426 + }
5599 5427
5600 - $content = "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "...</p>";
5601 - $content .= "<div class='vipps-qr-purchase' style='visibility:hidden'>";
5602 - $content .= "<a href='javascript:void(0)' class='single-product button vipps-buy-now $bclass' data-vipps-autostart='true' data-vipps-purchase='single' data-product_id='{$args['product_id']}' data-variation_id='{$args['variation_id']}' data-product_sku='{$args['sku']}' data-quantity='{$args['quantity']}' title='{$btitle}';
5603 - >";
5604 - $content .= $this->get_html_button_for_context('global');
5605 - $content .= "</a>";
5606 - $content .= "</div>";
5607 -
5608 - return $content;
5428 + return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args);
5609 5429 }
5610 5430
5611 5431 public function vipps_express_checkout_consistency_check() {
5612 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.
@@ -5630,10 +5450,9 @@
5630 5450
5631 5451 add_filter('woo_vipps_express_checkout_consistent', '__return_true');
5632 5452 }
5633 5453
5634 - // This is a landing page for the express checkout of the normal cart - it is done like this because this could take time on slower hosts.
5635 - // IOK 2026-09-09 - nowadays this is only used for compatibility mode. It will automatically start express checkout of the current cart when reached.
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.
5636 5455 public function vipps_express_checkout() {
5637 5456 // Some checks are made in template_redirect, we check here if they are ok IOK 2026-09-21
5638 5457 if (!apply_filters('woo_vipps_express_checkout_consistent', false)) {
5639 5458 $content = __('Link expired, please try again', 'woo-vipps');
@@ -5638,30 +5457,217 @@
5638 5457 if (!apply_filters('woo_vipps_express_checkout_consistent', false)) {
5639 5458 $content = __('Link expired, please try again', 'woo-vipps');
5640 5459 return $content;
5641 5460 }
5642 -
5461 +
5462 + add_filter('body_class', function ($classes) {
5463 + $classes[] = 'vipps-express-checkout';
5464 + $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24
5465 + return apply_filters('woo_vipps_express_checkout_body_class', $classes);
5466 + });
5467 +
5643 5468 do_action('woo_vipps_express_checkout_page');
5644 5469
5645 - $payment_method = $this->get_payment_method_name();
5646 - $btitle = esc_attr(sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method));
5647 - $bclass = esc_attr($payment_method);
5648 - $sec = esc_attr($_REQUEST['sec']);
5649 - $content = "";
5650 - $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "...</p>";
5651 - $content .= '<div class="vipps-cart-purchase" style="visibility:hidden">"';
5652 - $content .= "<a href='javascript:void(0)' class='vipps-express-checkout short $bclass' data-vipps-autostart='true' data-sec='$sec' title='$btitle'>";
5653 - $content .= $this->get_html_button_for_context('global');
5654 - $content .="</a>";
5655 - $content .="</div>";
5470 + return $this->express_checkout_page_html(true, 'do_express_checkout');
5471 + }
5656 5472
5657 - return $content;
5473 + // This method tries to ensure that a customer does not 'lose' the return page and
5474 + // starts ordering the same products twice. IOK 2020-01-22
5475 + protected function validate_express_checkout_orderspec ($orderspec) {
5476 + if (empty($orderspec)) return true; // It's not a duplicate, it's nothing.
5477 +
5478 + // First build for the current order an array of hash-tables keyed by prodid, varid and quantity.
5479 + $orderset = array();
5480 + foreach($orderspec as $entry) $orderset[] = join(':', $entry);
5481 +
5482 + // Then get open orders
5483 + $sessionorders = array();
5484 + $sessionorderdata = WC()->session->get('_vipps_session_orders');
5485 + if ($sessionorderdata) {
5486 + foreach(array_keys($sessionorderdata) as $oid) {
5487 + $orderobject = wc_get_order($oid);
5488 + // Check to see that this hasn't been deleted yet IOK 2020-01-07
5489 + if ($orderobject instanceof WC_Order) {
5490 + $sessionorders[] = $orderobject;
5491 + }
5492 + }
5493 + }
5494 + // Nothing more to do here
5495 + if (empty($sessionorders)) return true;
5496 +
5497 + // And create a similar hash table for each of the open orders
5498 + $openorderdata = array();
5499 + foreach ($sessionorders as $open_order) {
5500 + $status = $open_order->get_status();
5501 + if ($status == 'cancelled' || $status == 'pending') continue;
5502 + $when = strtotime($open_order->get_date_modified());
5503 + $cutoff = $when + apply_filters('woo_vipps_recent_order_cutoff', (5*60));
5504 + if (time() > $cutoff) {
5505 + continue;
5506 + }
5507 + $orderdata = array();
5508 + foreach($open_order->get_items() as $item) {
5509 + $productspec = $item->get_product_id() . ':' . $item->get_variation_id() . ':' . $item->get_quantity();
5510 + $orderdata[] = $productspec;
5511 + }
5512 + $openorderdata[]=$orderdata;
5513 + }
5514 +
5515 + // Now: For each entry in the orderhash, check if there is an order that has a) all of them and b) not any more of them.
5516 + foreach($openorderdata as $prevorder) {
5517 + $a = array_diff($prevorder, $orderset);
5518 + $b = array_diff($orderset, $prevorder);
5519 + if (empty($a) && empty($b)) {
5520 + $this->log(__("It seems a customer is trying to re-order product(s) recently bought in the same session, asking user for confirmation", 'woo-vipps'), 'info');
5521 + return false;
5522 + }
5523 + }
5524 + // Else, order is good.
5525 + return true;
5658 5526 }
5659 5527
5528 + // Returns a triple of productid, variantid and quantity from an array of arguments which can pass either these or a SKU value.
5529 + // Return value is like in a cart.
5530 + // Used to create an order in express checkout, and to see that this order isn't a repeat. IOK 2020-01-22
5531 + protected function get_orderspec_from_arguments ($productinfo) {
5532 + if (!$productinfo) return array();
5533 + $variantid = 0;
5534 + $productid = 0;
5535 + $quantity = intval(@$productinfo['quantity']);
5536 + if (!$quantity) $quantity = 1;
5537 + if (isset($productinfo['sku']) && $productinfo['sku']) {
5538 + $sku = $productinfo['sku'];
5539 + $skuid = wc_get_product_id_by_sku($sku);
5540 + $product = wc_get_product($skuid);
5541 + $parentid = $product ? $product->get_parent_id() : null;
5542 + if ($product) {
5543 + if ($parentid) {
5544 + $variantid = $skuid; $productid = $parentid;
5545 + } else {
5546 + $productid = $skuid;
5547 + }
5548 + }
5549 + } else if (isset($productinfo['product_id']) && $productinfo['product_id']) {
5550 + $productid = intval($productinfo['product_id']);
5551 + $variantid = intval(@$productinfo['variation_id']);
5552 + }
5553 + if ($productid) return array(array('product_id'=>$productid, 'variation_id'=>$variantid, 'quantity'=>$quantity));
5554 + return array();
5555 + }
5556 + // If no productinfo, this will produce an orderspec from the current cart IOK 2020-01-24
5557 + protected function get_orderspec_from_cart () {
5558 + $cartitems = WC()->cart->get_cart();
5559 + $orderspec = array();
5560 + foreach($cartitems as $item => $values) {
5561 + $orderspec[] = array('product_id'=>$values['product_id'], 'variation_id'=>$values['variation_id'], 'quantity'=>$values['quantity']);
5562 + }
5563 + return $orderspec;
5564 + }
5565 +
5566 + // Used as a landing page for launching express checkout - borh for the cart and for single products. IOK 2018-09-28
5567 + // Returns the html. LP 2026-08-27
5568 + protected function express_checkout_page_html($execute,$action,$productinfo=null) {
5569 + $gw = $this->gateway();
5570 +
5571 + $expressCheckoutMessages = array();
5572 + $expressCheckoutMessages['termsAndConditionsError'] = __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' );
5573 + $expressCheckoutMessages['temporaryError'] = sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name());
5574 + $expressCheckoutMessages['successMessage'] = sprintf(__('To the %1$s app!','woo-vipps'), $this->get_payment_method_name());
5575 +
5576 + wp_register_script('vipps-express-checkout',plugins_url('js/express-checkout.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/express-checkout.js"), 'true');
5577 + wp_localize_script('vipps-express-checkout', 'VippsCheckoutMessages', $expressCheckoutMessages);
5578 + wp_enqueue_script('vipps-express-checkout');
5579 + // If we have a valid nonce when we get here, just call the 'create order' bit at once. Otherwise, make a button
5580 + // to actually perform the express checkout.
5581 + $buttonhtml = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button());
5582 +
5583 +
5584 +
5585 + $orderspec = $this->get_orderspec_from_arguments($productinfo);
5586 + if (empty($orderspec)) {
5587 + $orderspec = $this->get_orderspec_from_cart();
5588 + }
5589 + $orderisOK = $this->validate_express_checkout_orderspec($orderspec);
5590 + $orderisOK = apply_filters('woo_vipps_validate_express_checkout_orderspec', $orderisOK, $orderspec);
5591 +
5592 + $askForTerms = function_exists('wc_terms_and_conditions_checkbox_enabled') ? wc_terms_and_conditions_checkbox_enabled() : true;
5593 + $askForTerms = $askForTerms && ($gw->get_option('expresscheckout_termscheckbox') == 'yes');
5594 + $askForTerms = apply_filters('woo_vipps_express_checkout_terms_and_conditions_checkbox_enabled', $askForTerms);
5595 +
5596 + $askForConfirmationHTML = '';
5597 + if (!$orderisOK) {
5598 + $header = __("Are you sure?",'woo-vipps');
5599 + $body = __("You recently completed an order with exactly the same products as you are buying now. There should be an email in your inbox from the previous purchase. Are you sure you want to order again?",'woo-vipps');
5600 + $askForConfirmationHTML = apply_filters('woo_vipps_ask_user_to_confirm_repurchase', "<h2 class='confirmVippsExpressCheckoutHeader'>$header</h2><p>$body</p>");
5601 + }
5602 + // Should we go directly to checkout, or do we need to stop and ask the user something (for instance?) IOK 2010-01-20
5603 + $execute = $execute && $orderisOK && !$askForTerms;
5604 + $execute = apply_filters('woo_vipps_checkout_directly_to_vipps', $execute, $productinfo);
5605 +
5606 + $content = $this->spinner();
5607 +
5608 + // We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24
5609 + // The form data below is sent on order creation; the sec is also used to poll session status
5610 + $classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout");
5611 + $content .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>";
5612 + $content .= "<input type='hidden' name='action' value='" . esc_attr($action) ."'>";
5613 + if ($this->gateway()->get_option('vippsorderattribution') == 'yes') {
5614 + // This is for the new order attribution feature of woo. IOK 2024-01-09
5615 + $content .= '<input type="hidden" id="vippsorderattribution" value="1" />';
5616 + ob_start();
5617 + do_action( 'woocommerce_after_order_notes');
5618 + $content .= ob_get_clean();
5619 + }
5620 + $content .= wp_nonce_field('do_express','sec',1,false);
5621 +
5622 + $termsHTML = '';
5623 + if ($askForTerms) {
5624 + // Include shop terms
5625 + ob_start();
5626 + wc_get_template('checkout/terms.php');
5627 + $termsHTML = ob_get_clean();
5628 + $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML);
5629 + }
5630 + $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML);
5631 +
5632 + if ($productinfo) {
5633 + foreach($productinfo as $key=>$value) {
5634 + $k = esc_attr($key);
5635 + $v = esc_attr($value);
5636 + $content .= "<input type='hidden' name='$k' value='$v' />";
5637 + }
5638 + }
5639 + ob_start();
5640 + $content .= do_action('woo_vipps_express_checkout_orderspec_form', $productinfo);
5641 + $content .= ob_get_clean();
5642 + $content .= "</form>";
5643 +
5644 + $extraHTML = apply_filters('woo_vipps_express_checkout_final_html', '', $termsHTML,$askForConfirmationHTML);
5645 + $pressTheButtonHTML = "";
5646 + if (empty($termsHTML) && empty($askForConfirmationHTML) && empty($extraHTML)) {
5647 + $pressTheButtonHTML = "<p id=waiting>" . sprintf(__('Ready for %1$s - press the button', 'woo-vipps'), Vipps::ExpressCheckoutName()) . "</p>";
5648 + }
5649 +
5650 + if ($execute) {
5651 + $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "</p>";
5652 + $content .= "<div id='vipps-status-message'></div>";
5653 + return $this->special_page_html('', $content);
5654 + } else {
5655 + $content .= $askForConfirmationHTML;
5656 + $content .= $extraHTML;
5657 + $content .= $termsHTML;
5658 + $content .= apply_filters('woo_vipps_express_checkout_validation_elements', '');
5659 + $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $this->get_payment_method_name());
5660 + $content .= "<div class='vipps_buy_now_wrapper noloop'><a href='#' id='do-express-checkout' class='vipps-express-checkout' title='$title'>$buttonhtml</a></div>";
5661 + $content .= "<div id='vipps-status-message'></div>";
5662 + return $this->special_page_html('', $content);
5663 + }
5664 + }
5665 +
5666 +
5660 5667 // Called in template_redirect before we get to the wait-for-payment page IOK 2026-09-21
5661 5668 private function handle_payment_poll_and_redirect () {
5662 5669 $orderid = WC()->session->get('_vipps_pending_order');
5663 -
5664 5670 $order = null;
5665 5671 $gw = $this->gateway();
5666 5672
5667 5673 // Failsafe for when the session disappears IOK 2018-11-19
@@ -5690,14 +5696,9 @@
5690 5696 $session = WC()->session;
5691 5697 if (!$session->has_session()) {
5692 5698 $session->set_customer_session_cookie(true);
5693 5699 }
5694 -
5695 - $sessionorders= WC()->session->get('_vipps_session_orders');
5696 - $sessionorders[$orderid] = 1;
5697 - WC()->session->set('_vipps_session_orders',$sessionorders);
5698 5700 $session->set('_vipps_pending_order', $orderid);
5699 - WC()->session->save_data();
5700 5701 }
5701 5702 }
5702 5703
5703 5704 $deleted_order=0;
@@ -5703,9 +5704,9 @@
5703 5704 $deleted_order=0;
5704 5705 if ($orderid && !$order) {
5705 5706 // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled.
5706 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
5707 - $this->log(sprintf(__("In order return: The order %1\$d seems to be deleted", 'woo-vipps'), $orderid), 'debug');
5708 + $this->log(__("In order return: The order %1\$d seems to be deleted", 'woo-vipps'), 'debug');
5708 5709 $deleted_order=1;
5709 5710 }
5710 5711
5711 5712 if (!$order && !$deleted_order) wp_die(__('Unknown order', 'woo-vipps'));
@@ -5715,13 +5716,12 @@
5715 5716
5716 5717 // This is for debugging only - set to false to ensure we wait for the callback. IOK 2023-08-04
5717 5718 $do_poll = true;
5718 5719
5719 - // Do a single poll here to check and set the order status at Woo using the order status at Vipps IOK 2026-09-29
5720 + // Still pending, no callback. Make a call to the server as the order might not have been created. IOK 2018-05-16
5720 5721 if ($do_poll && $status == 'pending') {
5721 - // We will do *one* poll before waiting for the callback (for a while, at least.) IOK 2026-09-29
5722 - $newstatus = $gw->poll_and_check_order_status($order);
5723 - $this->log(sprintf(__("In order return: Order status of %1\$d is %2\$s", 'woo-vipps'), $orderid, $newstatus), 'info');
5722 + // Just in case the callback hasn't come yet, do a quick check of the order status at Vipps.
5723 + $newstatus = $gw->callback_check_order_status($order);
5724 5724 if ($status != $newstatus) {
5725 5725 $status = $newstatus;
5726 5726 clean_post_cache($orderid);
5727 5727 $order = wc_get_order($orderid); // Reload order object
@@ -5729,10 +5729,8 @@
5729 5729 } else {
5730 5730 // No need to do anyting here. IOK 2020-01-26
5731 5731 }
5732 5732
5733 - // Actually, this may cause a second poll if the first left us pending. Should be rewritten - but *mostly* it will just check
5734 - // the payment status at Vipps without polls, which will tell us if the payment succeeded in case people use custom order statuses and so on. IOK 2026-09-29
5735 5733 $payment = 'notchecked';
5736 5734 if ($do_poll) {
5737 5735 $payment = $deleted_order ? 'cancelled' : $gw->check_payment_status($order);
5738 5736 }
@@ -5767,10 +5765,8 @@
5767 5765 // If not, enqueue the status checker IOK 2026-09-21
5768 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');
5769 5767 }
5770 5768
5771 - $this->log(sprintf(__("Order status of %1\$d not ready in order return: payment status %2\$s", 'woo-vipps'), $orderid, $payment), 'info');
5772 -
5773 5769 // Communicate this to the shortcode IOK 2026-09-21
5774 5770 add_filter('woo_vipps_wait_for_payment_status', function () use($orderid, $status, $payment) {
5775 5771 return ['orderid'=>$orderid, 'status'=>$status, 'payment'=>$payment];
5776 5772 });
@@ -5777,8 +5773,9 @@
5777 5773
5778 5774 }
5779 5775
5780 5776 public function vipps_wait_for_payment() {
5777 +
5781 5778 // This will have been computed in template_redirect, but the status will be either still pending or failed. IOK 2026-09-21
5782 5779 $data = apply_filters('woo_vipps_wait_for_payment_status', []);
5783 5780
5784 5781 $orderid = $data['orderid'] ?? 0;
@@ -5818,8 +5815,9 @@
5818 5815 $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>";
5819 5816 $content .= "<input type='hidden' name='action' value='check_order_status'>";
5820 5817 $content .= wp_nonce_field('vippsstatus','sec',1,false);
5821 5818 $content .= "</form>";
5819 +
5822 5820
5823 5821 $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>';
5824 5822 $content .= "<p>" . __('An error occured during order confirmation. The error has been logged. Please contact us to determine the status of your order', 'woo-vipps') . "</p>";
5825 5823 $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>';