PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.3.1
Pay with Vipps and MobilePay for WooCommerce v6.3.1
6.3.1 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 All 190 releases
← All changes | payment/WC_Gateway_Vipps.class.php +95 -175 6.1.4 → 6.3.1 View file →
@@ -215,8 +215,26 @@
215 215 add_action('woocommerce_order_status_refunded', array($this, 'maybe_refund_order'), 9, 1);
216 216
217 217 // Possibly delete orders that never went anywhere
218 218 add_action('woocommerce_order_status_pending_to_cancelled', array($this, 'maybe_delete_order'), 99999, 1);
219 +
220 + // Disable emails for cancelled express orders that never went anywhere IOK 2026-09-09
221 + add_filter('woocommerce_email_enabled_cancelled_order', function ( $enabled, $order, $email ) {
222 + if ( ! $order instanceof WC_Order ) {
223 + return $enabled;
224 + }
225 + $pm = $order->get_payment_method();
226 + if (! Vipps::is_vipps_order($pm)){
227 + return $enabled;
228 + }
229 + $is_vipps_express = (bool) $order->get_meta( '_vipps_express_checkout' );
230 + $has_billing_email = (bool) $order->get_billing_email();
231 + if ( $is_vipps_express) {
232 + return false;
233 + }
234 + return $enabled;
235 + }, 10, 3);
236 +
219 237 // Handle orders when authorized
220 238 add_action('woocommerce_payment_complete', array($this, 'order_payment_complete'), 10, 1);
221 239
222 240 // when an order is complete, we need to check if there is reserved amount that is not captured
@@ -280,10 +298,8 @@
280 298 public function maybe_cancel_reserved_amount ($orderid) {
281 299 $order = wc_get_order($orderid);
282 300 if (!$order) return;
283 301 if (! Vipps::is_vipps_order($order)) return false;
284 - // Cannot partially cancel legacy ecom orders
285 - if ('epayment' != $order->get_meta('_vipps_api')) return false;
286 302
287 303 // Check that the normal maybe_capture_order hook has actually ran *and* done something,
288 304 // it's only after this we know we have captured 'everything' so if there is anything left,
289 305 // it should be cancelled. IOK 2025-05-04
@@ -585,9 +601,9 @@
585 601 // Webhook callbacks do not pass GET arguments at all, but do provide an X-Vipps-Authorization header for verification. IOK 2023-12-19
586 602 public function webhook_callback_url () {
587 603 $url = home_url("/", 'https');
588 604 $queryargs = ['callback'=>'webhook'];
589 - $forwhat = 'wc_gateway_vipps'; // Same callback as for ecom, checkout, express checkout
605 + $forwhat = 'wc_gateway_vipps'; // Same callback as for epayment, checkout, express checkout
590 606 // HTTPS required. IOK 2018-05-18
591 607 // If the user for some reason hasn't enabled pretty links, fall back to ancient version. IOK 2018-04-24
592 608 if ( !get_option('permalink_structure')) {
593 609 $queryargs['wc-api'] = $forwhat;
@@ -605,22 +621,8 @@
605 621 }
606 622 public function shipping_details_callback_url($token='',$reference=0) {
607 623 return $this->make_callback_urls('vipps_shipping_details',$token,$reference);
608 624 }
609 - // Callback for the consetn removal callback. Must use template redirect directly, because wc-api doesn't handle DELETE.
610 - // IOK 2018-05-18
611 - public function consent_removal_callback_url () {
612 - $queryargs = [];
613 - $url = home_url("/", 'https');
614 - if ( !get_option('permalink_structure')) {
615 - $queryargs['vipps-consent-removal']=1;
616 - } else {
617 - $url = trailingslashit(home_url('vipps-consent-removal', 'https'));
618 - }
619 - // And we need to add an empty "callback" query arg as the very last arg to receive the actual callback.
620 - // We can't use add_query_arg for that, as an empty argument will remove the equals-sign.
621 - return add_query_arg($queryargs, $url) . "&callback=";
622 - }
623 625
624 626 // Allow user to select the template to be used for the special Vipps MobilePay pages. IOK 2020-02-17
625 627 public function get_theme_page_templates() {
626 628 if (!$this->page_templates) {
@@ -632,22 +634,8 @@
632 634 }
633 635 return $this->page_templates;
634 636 }
635 637
636 - // We can't use get_pages to get a default list of pages for our settings, because it triggers
637 - // actions that can be used by other plugins. Therefore we must use the database directly and cache the results. IOK 2023-08-22
638 - public function get_pagelist () {
639 - if (!$this->page_list) {
640 - global $wpdb;
641 - $page_list = array(''=>__('Use a simulated page (default)', 'woo-vipps'));
642 - foreach($wpdb->get_results("SELECT ID,post_title FROM {$wpdb->prefix}posts WHERE post_type='page' and post_status='publish'") as $page) {
643 - $page_list[$page->ID] = $page->post_title;
644 - }
645 - $this->page_list = $page_list;
646 - }
647 - return $this->page_list;
648 - }
649 -
650 638 // Check to see if the product in question can be bought with express checkout IOK 2018-12-04
651 639 public function product_supports_express_checkout($product) {
652 640 // IOK 2023-12-12 Can only support express checkout for Vipps - not MobilePay (yet!)
653 641 // IOK 2025-09-01 Now supports mobilepay
@@ -799,9 +787,9 @@
799 787 $order->save();
800 788 }
801 789
802 790 // IOK 2024-09-01 In general, we can refund most Vipps Mobilepay orders through the api,
803 - // however, this is not the case for the Bank Transfer method available through Vipps Checkout.
791 + // however, this is not the case for the Bank Transfer method available through Checkout.
804 792 public function can_refund_order( $order ) {
805 793 $method = $order->get_meta('_vipps_api');
806 794 switch ($method) {
807 795 case 'banktransfer':
@@ -809,9 +797,9 @@
809 797 break;
810 798 case 'epayment':
811 799 return true;
812 800 break;
813 - // Default is old-style ecom v2.
801 + // Default is true; but the above are exhaustive IOK 2026-08-18
814 802 default:
815 803 return true;
816 804 break;
817 805 }
@@ -1057,9 +1045,8 @@
1057 1045 global $Vipps;
1058 1046
1059 1047 // Used for defaults in the admin interface; however this functions is called a loot more often than that.
1060 1048 $page_templates = $this->get_theme_page_templates();
1061 - $page_list = $this->get_pagelist();
1062 1049
1063 1050 $orderprefix = $Vipps->generate_order_prefix();
1064 1051
1065 1052 // Default handling based on other parameters and earlier values.
@@ -1070,16 +1057,15 @@
1070 1057 if (class_exists('VippsWooLogin')) {
1071 1058 $woodefault = 'yes' === get_option('woocommerce_enable_signup_and_login_from_checkout');
1072 1059 if ($woodefault) {
1073 1060 $expresscreateuserdefault = "yes";
1074 - // $vippscreateuserdefault = "yes"; // However, for Vipps Checkout the email address is freetext so we'll treat the default a bit different.
1061 + // $vippscreateuserdefault = "yes"; // However, for Checkout the email address is freetext so we'll treat the default a bit different.
1075 1062 }
1076 1063 }
1077 1064
1078 - // We will only show the Vipps Checkout options if the user has activated the feature (thus creating the pages involved etc). IOK 2021-10-01
1065 + // We will only show the Checkout options if the user has activated the feature (thus creating the pages involved etc). IOK 2021-10-01
1079 1066 $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false);
1080 1067
1081 -
1082 1068 // This is used for new options,to set reasonable defaults based on older settings. We can't use WC_Settings->get_option for this unfortunately.
1083 1069 $current = get_option('woocommerce_vipps_settings');
1084 1070 // New defaults based on old defaults
1085 1071 $default_static_shipping_for_checkout = 'no';
@@ -1242,9 +1228,9 @@
1242 1228 'description' => __('Your phone number where Porterbuddy may send you important messages. Format must be MSISDN (including country code). Example: "4791234567"','woo-vipps'),
1243 1229 'default' => '',
1244 1230 ),
1245 1231
1246 - // Vipps checkout *shipping options* - extra shipping options that only work with Vipps Checkout
1232 + // Vipps checkout *shipping options* - extra shipping options that only work with Checkout
1247 1233 'vcs_helthjem' => array(
1248 1234 'title' => __('Helthjem', 'woo-vipps'),
1249 1235 'label' => sprintf(__('Support Helthjem as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()),
1250 1236 'type' => 'checkbox',
@@ -1279,9 +1265,9 @@
1279 1265 ),
1280 1266
1281 1267 );
1282 1268
1283 - /* Support for *certain* external payment methods in Vipps Checkout. IOK 2024-05-27 */
1269 + /* Support for *certain* external payment methods in Checkout. IOK 2024-05-27 */
1284 1270 $externals = [];
1285 1271 $external_payment_fields = [];
1286 1272 $allow_external_payments = $this->allow_external_payments_in_checkout();
1287 1273 if ($allow_external_payments) {
@@ -1604,22 +1590,24 @@
1604 1590 'description' => __('Turn this on to add support for Woos Order Attribution API for Checkout and Express Checkout. Some stores have reported problems when using this API together with Vipps, so be sure to test this if you turn it on.', 'woo-vipps'),
1605 1591 ),
1606 1592
1607 1593 'vippsspecialpagetemplate' => array(
1608 - 'title' => sprintf(__('Override page template used for the special %1$s pages', 'woo-vipps'), Vipps::CompanyName()),
1594 + 'title' => sprintf(__('Legacy: Override page template used for the special %1$s page', 'woo-vipps'), Vipps::CompanyName()),
1609 1595 'label' => sprintf(__('Use specific template for %1$s', 'woo-vipps'), Vipps::CompanyName()),
1610 1596 'type' => 'select',
1611 1597 'options' => $page_templates,
1612 - 'description' => sprintf(__('Use this template from your theme or child-theme to display all the special %1$s pages. You will probably want a full-width template and it should call \'the_content()\' normally.', 'woo-vipps'), Vipps::CompanyName()),
1598 + 'description' => sprintf(__('Use this template from your theme or child-theme for the special %1$s page.<br>Legacy: This is not necessary anymore - you should instead choose a template by editing the page like any other page.','woo-vipps'), Vipps::CompanyName()),
1613 1599 'default' => ''),
1614 1600
1601 + // Deprecated, not shown anymore: TODO: remove this option in future. LP 2026-09-01
1615 1602 'vippsspecialpageid' => array(
1616 1603 'title' => sprintf(__('Use a real page ID for the special %1$s pages - neccessary for some themes', 'woo-vipps'), Vipps::CompanyName()),
1617 1604 'label' => __('Use a real page ID', 'woo-vipps'),
1618 1605 'type' => 'select',
1619 - 'options' => $page_list,
1606 + 'options' => [],
1620 1607 'description' => sprintf(__('Some very few themes do not work with the simulated pages used by this plugin, and needs a real page ID for this. Choose a blank page for this; the content will be replaced, but the template and other metadata will be present. You only need to use this if the plugin seems to break on the special %1$s pages.', 'woo-vipps'), Vipps::CompanyName()),
1621 - 'default'=>''),
1608 + 'default' => ''
1609 + ),
1622 1610
1623 1611 'sendreceipts' => array(
1624 1612 'title' => __("Send receipts and order confirmation info to the customers' app on completed purchases.", 'woo-vipps'),
1625 1613 'label' => sprintf(__("Send receipts to the customers %1\$s app", 'woo-vipps'), Vipps::CompanyName()),
@@ -1635,17 +1623,8 @@
1635 1623 'description' => sprintf(__('If set, this image will be uploaded to %1$s and used to profile your store in the %1$s app for links to the order confirmation etc', 'woo-vipps'), Vipps::CompanyName()),
1636 1624 'default' => 0,
1637 1625 ),
1638 1626
1639 -
1640 - 'use_flock' => array (
1641 - 'title' => __('Use flock() to lock orders for Express Checkout', 'woo-vipps'),
1642 - 'label' => __('Use flock() to lock orders for Express Checkout', 'woo-vipps'),
1643 - 'type' => 'checkbox',
1644 - 'description' => __('Use the flock() system call to ensure orders are only finalized once. You can use this for normal setups, but probably not on Windows with IIS, and possibly not on distributed filesystems like NFS. If you don\t know what it is, probably do not use it. If you get duplicated shipping lines on some express orders, you may try using this', 'woo-vipps'),
1645 - 'default' => 'no',
1646 - ),
1647 -
1648 1627 'delete_settings_on_deactivation' => array (
1649 1628 'title' => __('Delete plugin settings on deactivation', 'woo-vipps'),
1650 1629 'label' => __('Delete plugin settings on deactivation', 'woo-vipps'),
1651 1630 'type' => 'checkbox',
@@ -1783,9 +1762,9 @@
1783 1762 $ok = apply_filters('woo_vipps_is_available', $ok, $this);
1784 1763 return $ok;
1785 1764 }
1786 1765
1787 - // True if the alternative Vipps Checkout screen is both available and activated. Returns the page id of the checkout
1766 + // True if the alternative Checkout screen is both available and activated. Returns the page id of the checkout
1788 1767 // page for convenience. IOK 2021-10-01
1789 1768 public function vipps_checkout_available () {
1790 1769
1791 1770 if ($this->get_option('vipps_checkout_enabled') != 'yes') return false;
@@ -1856,9 +1835,8 @@
1856 1835 wc_add_notice(sprintf(__('Unfortunately, the %1$s payment method is currently unavailable. Please choose another method.','woo-vipps'), $this->get_payment_method_name()),'error');
1857 1836 return [];
1858 1837 }
1859 1838
1860 -
1861 1839 // From the request, get either [billing_phone] => or [vipps phone]
1862 1840 $phone = '';
1863 1841 if (isset($_POST['vippsphone'])) {
1864 1842 $phone = trim(sanitize_text_field($_POST['vippsphone']));
@@ -1951,9 +1929,8 @@
1951 1929 $limited_session = $this->generate_authtoken();
1952 1930 $returnurl = add_query_arg('ls',$limited_session,$returnurl);
1953 1931 $returnurl = add_query_arg('id', $order_id, $returnurl);
1954 1932
1955 -
1956 1933 try {
1957 1934 // If the order was 'failed', it isnt any more! yet!
1958 1935 if ($order->get_status() == 'failed') {
1959 1936 $order->set_status('pending', __('Setting order status to pending to start payment', 'woo-vipps'));
@@ -2001,12 +1978,14 @@
2001 1978 $order->update_meta_data('_vipps_init_timestamp',$vippstamp);
2002 1979 $order->update_meta_data('_vipps_orderurl', $url);
2003 1980
2004 1981 $order->update_meta_data('_vipps_status','INITIATE'); // INITIATE right now
2005 - $order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), $this->get_payment_method_name()));
2006 - $order->add_order_note(sprintf(__('Awaiting %1$s payment confirmation','woo-vipps'), $this->get_payment_method_name()));
1982 +
1983 + $name = $this->get_payment_method_name();
1984 + $order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), $name));
1985 + $order->add_order_note(sprintf(__('Awaiting %1$s payment confirmation','woo-vipps'),$name));
1986 +
2007 1987 $order->save();
2008 -
2009 1988 // Create a signal file that we can check without calling wordpress to see if our result is in IOK 2018-05-04
2010 1989 try {
2011 1990 $Vipps->createCallbackSignal($order);
2012 1991 } catch (Exception $e) {
@@ -2011,9 +1990,8 @@
2011 1990 $Vipps->createCallbackSignal($order);
2012 1991 } catch (Exception $e) {
2013 1992 // Could not create a signal file, but that's ok.
2014 1993 }
2015 -
2016 1994 do_action('woo_vipps_before_redirect_to_vipps',$order_id);
2017 1995
2018 1996 // This will send us to a receipt page where we will do the actual work. IOK 2018-04-20
2019 1997 return array('result'=>'success','redirect'=>$url);
@@ -2142,12 +2120,11 @@
2142 2120 if ($api == 'banktransfer') {
2143 2121 // This is an error - we should not ever get to the 'capture' branch if we are a banktransfer payment.
2144 2122 // IOK 2024-01-09
2145 2123 $content = [];
2146 - } elseif ($api == 'epayment') {
2124 + } else {
2125 + // Now the only other api is 'epayment' IOK 2026-08-18
2147 2126 $content = $this->api->epayment_capture_payment($order,$amount,$requestid);
2148 - } else {
2149 - $content = $this->api->capture_payment($order,$amount,$requestid);
2150 2127 }
2151 2128 } catch (TemporaryVippsApiException $e) {
2152 2129 $this->log(sprintf(__('Could not capture %1$s payment for order id:', 'woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id() . "\n" .$e->getMessage(),'error');
2153 2130 $this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . "\n" . $e->getMessage());
@@ -2198,8 +2175,9 @@
2198 2175 return false;
2199 2176 }
2200 2177 // We'll use the same transaction id for all cancel jobs, as we can only do it completely. IOK 2018-05-07
2201 2178 // For epayment, partial cancellations will be possible. IOK 2022-11-12
2179 + // IOK 2026-08-18 actually, epayment does *not* support partial cancellation - all remaining funds are cancelled.
2202 2180 $api = $order->get_meta('_vipps_api');
2203 2181 try {
2204 2182 $requestid = "";
2205 2183 if ($api == 'banktransfer') {
@@ -2204,22 +2182,14 @@
2204 2182 $requestid = "";
2205 2183 if ($api == 'banktransfer') {
2206 2184 // If we are here, and the order is somehow not captured, just do nothing. IOK 2024-01-09
2207 2185 $content = [];
2208 - } elseif ($api == 'epayment') {
2186 + } else {
2187 + // api is here 'epayment'. IOK 2026-07-18
2209 2188 $requestid = 1;
2210 2189 // This will cancel any remaining, not-captured amount IOK 2026-01-28
2211 2190 $content = $this->api->epayment_cancel_payment($order,$requestid);
2212 - } else {
2213 - // If we have captured the order, we can't cancel it with the ecom API IOK 2018-05-07
2214 - $captured = intval($order->get_meta('_vipps_captured'));
2215 - if ($captured>0) {
2216 - $msg = sprintf(__('Cannot cancel a captured %1$s transaction - use refund instead', 'woo-vipps'), "ECOM " . $this->get_payment_method_name());
2217 - $this->adminerr($msg);
2218 - return false;
2219 - }
2220 - $content = $this->api->cancel_payment($order,$requestid);
2221 - }
2191 + }
2222 2192 } catch (TemporaryVippsApiException $e) {
2223 2193 $this->log(sprintf(__('Could not cancel %1$s payment for order_id:', 'woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id() . "\n" .$e->getMessage(),'error');
2224 2194 $this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage());
2225 2195 return false;
@@ -2233,9 +2203,8 @@
2233 2203 // the epay v2 API would return transactionInfo and Summary with the result, the new epayment api returns nothing.
2234 2204 // Removed epay branch 2025-08-12 IOK
2235 2205 $total = intval($order->get_meta('_vipps_amount'));
2236 2206 $captured = intval($order->get_meta('_vipps_captured'));
2237 -# $cancelled = $amount + intval($order->get_meta('_vipps_cancelled');
2238 2207 $cancelled = $total;
2239 2208 $remaining = $total - $captured - $cancelled;
2240 2209
2241 2210 // We need to assume it worked. Also, we can't do partial cancels yet, so just cancel everything.
@@ -2283,13 +2252,12 @@
2283 2252 if ($api == 'banktransfer') {
2284 2253 $msg = sprintf(__("Cannot refund bank transfer order %1\$d", 'woo-vipps'), $order->get_id());
2285 2254 $this->log($msg, 'error');
2286 2255 throw new Exception($msg);
2287 - } elseif ($api == 'epayment') {
2256 + } else {
2257 + // api is now 'epayment' IOK 2026-08-18
2288 2258 $content = $this->api->epayment_refund_payment($order,$requestid,$amount,$cents);
2289 - } else {
2290 - $content = $this->api->refund_payment($order,$requestid,$amount,$cents);
2291 - }
2259 + }
2292 2260
2293 2261 $currency = $order->get_currency();
2294 2262
2295 2263 // Previously, we got updated transaction info in a transactionInfo field. this is no longer provided,
@@ -2379,15 +2347,15 @@
2379 2347 // Default should never happen, but just to ensure we are in our enumeration
2380 2348 return "initiated";
2381 2349 }
2382 2350
2383 - // This does not normally call Vipps, so if you need to refresh status, please use callback_check_order_status first. IOK 2019-01-23
2351 + // This does not normally call Vipps, so if you need to refresh status, please use poll_and_check_order_status first. IOK 2019-01-23
2384 2352 public function check_payment_status($order) {
2385 2353 if (!$order) return 'cancelled';
2386 2354 $status = $this->interpret_vipps_order_status($order->get_meta('_vipps_status'));
2387 2355 // This can happen if the vipps status is set from the back end for instance. IOK 2020-08-14
2388 2356 if ($order->get_status() == 'pending' && $status != 'initiated') {
2389 - $this->callback_check_order_status($order);
2357 + $this->poll_and_check_order_status($order);
2390 2358 $order = wc_get_order($order->get_id()); // refresh to get the new status IOK 2021-01-20
2391 2359 $status = $this->interpret_vipps_order_status($order->get_meta('_vipps_status'));
2392 2360 }
2393 2361 return $status;
@@ -2392,9 +2360,9 @@
2392 2360 }
2393 2361 return $status;
2394 2362 }
2395 2363
2396 - // Called by callback_check_order_status and handle_callback to handle the situation where
2364 + // Called by poll_and_check_order_status and handle_callback to handle the situation where
2397 2365 // the payment method has been set to something else *after* Vipps has gotten the order.
2398 2366 // This happens very rarely for people who use Vipps as an external payment method in Klarna, so
2399 2367 // we only do it for orders that match this. IOK 2023-02-03
2400 2368 public function reset_erroneous_payment_method($order) {
@@ -2421,9 +2389,9 @@
2421 2389 }
2422 2390
2423 2391 // Check status of order at Vipps, in case the callback has been delayed or failed.
2424 2392 // Should only be called if in status 'pending'; it will modify the order when status changes.
2425 - public function callback_check_order_status($order, $allow_retry = true) {
2393 + public function poll_and_check_order_status($order, $allow_retry = true) {
2426 2394 global $Vipps;
2427 2395 $orderid = $order->get_id();
2428 2396
2429 2397 clean_post_cache($order->get_id());
@@ -2456,15 +2424,8 @@
2456 2424 }
2457 2425 // Something changed, so we are now going to sideeffect the order. IOK 2025-10-15
2458 2426 $this->log(sprintf(__("%1\$s poll: Handling order: ", 'woo-vipps'), Vipps::CompanyName()) . " " . $orderid, 'debug');
2459 2427
2460 - // If we are in the process of getting a callback from vipps, don't update anything. Currently, Woo/WP has no locking mechanism,
2461 - // and it isn't feasible to implement one portably. So this reduces somewhat the likelihood of races when this method is called
2462 - // and callbacks happen at the same time.
2463 - if (!$Vipps->lockOrder($order)) {
2464 - return $oldstatus;
2465 - }
2466 -
2467 2428 // Failsafe for rare bug when using Klarna Checkout with Vipps as an external payment method
2468 2429 // IOK 2024-01-09 ensure this is called only when order is complete/authorized
2469 2430 if ($ready) {
2470 2431 $this->reset_erroneous_payment_method($order);
@@ -2473,10 +2434,8 @@
2473 2434 $order->update_meta_data('_vipps_status',$newvippsstatus);
2474 2435
2475 2436 // Extract order metadata from either Checkout or Epayment - set below IOK 2025-08-13
2476 2437 if (!empty($paymentdetails)) {
2477 -
2478 -
2479 2438 // checkout has a string, epayment has an array with upper case "type" and apparently, cardBin IOK 2025-08-12
2480 2439 $paymentMethod = $paymentdetails['paymentMethod'] ?? "epayment";
2481 2440 // After normalization, all APIs will have data here.
2482 2441 $details = $paymentdetails['paymentDetails'];
@@ -2498,9 +2457,8 @@
2498 2457
2499 2458 } catch (Exception $e) {
2500 2459 $this->log(sprintf(__("Error getting payment details from %1\$s for order_id:",'woo-vipps'), $this->get_payment_method_name()) . $orderid . "\n" . $e->getMessage(), 'error');
2501 2460 clean_post_cache($order->get_id());
2502 - $Vipps->unlockOrder($order);
2503 2461 return $oldstatus;
2504 2462 }
2505 2463 $order->save();
2506 2464
@@ -2533,9 +2491,8 @@
2533 2491 $this->log(sprintf(__("No address information for order %2\$d, but there still might be an active %1\$s session for it, so do not cancel it.", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id()));
2534 2492 }
2535 2493 }
2536 2494 clean_post_cache($order->get_id());
2537 - $Vipps->unlockOrder($order);
2538 2495 return $oldstatus;
2539 2496 }
2540 2497 }
2541 2498
@@ -2571,9 +2528,8 @@
2571 2528
2572 2529 $order->save();
2573 2530 clean_post_cache($order->get_id());
2574 2531 $newstatus = $order->get_status();
2575 - $Vipps->unlockOrder($order);
2576 2532 return $newstatus;
2577 2533 }
2578 2534
2579 2535 // IOK 2020-01-20 Previously was just a debugging tool, then was used to update postmeta values. Now is used as the main source of info
@@ -2689,73 +2645,11 @@
2689 2645 if (in_array($newstatus, ['authorized', 'complete'])) {
2690 2646 $ready = true;
2691 2647 }
2692 2648
2693 -
2694 - // if this is *express - not checkout * and there is no user information, this is probably because we only get that when adding the 'address' scope.
2695 - // if we didn't want the address, we now need to ask for user details using the login get_userinfo api. IOK 2025-08-12
2696 - // This is also the only way to get "email_verified", so we may want to add a setting that always calls this if neccessary. IOK 2025-08-13
2697 - // Also we don't get this when the state is different from AUTHORIZED. Especially not ABORTED.
2698 - // IOK 2025-09-29: This is *no longer the case* . We actually now get userDetails every time we add the relevant scopes,
2699 - // so this is now probably dead code.
2700 - if ($ready && $express && !$checkout_session && !isset($result['userDetails'])) {
2701 -
2702 - $sub = isset($result['profile']) && isset($result['profile']['sub']) ? $result['profile']['sub'] : null;
2703 - $userinfo = [];
2704 - if (!$sub) {
2705 - // This should never happen, but be prepared
2706 - $message = sprintf(__("Could not get user info for order %1\$d using the userinfo API: %2\$s. Please use the 'get complete transaction details' on the button to try to recover this. ", 'woo-vipps'), $order->get_id(), "No 'sub' passed for user ID" );
2707 - $order->add_order_note($message);
2708 - $this->log($message , "error");
2709 - } else {
2710 - // If this happens, the merchant *may* be able to retrieve the information from Vipps so add a note for it.
2711 - try {
2712 - $userinfo = $this->api->get_userinfo($sub);
2713 - } catch (Exception $e) {
2714 - $message = sprintf(__("Could not get user info for order %1\$d using the userinfo API: %2\$s. Please use the 'get complete transaction details' on the button to try to recover this. ", 'woo-vipps'), $order->get_id(), $e->getMessage());
2715 - $order->add_order_note($message);
2716 - $this->log($message, 'woo-vipps', "error");
2717 - }
2718 - }
2719 - if ($userinfo) {
2720 - $userDetails = array(
2721 - 'email_verified' => $userinfo['email_verified'],
2722 - 'email' => $userinfo['email'],
2723 - 'firstName' => $userinfo['given_name'] ?? '',
2724 - 'lastName' => $userinfo['family_name'] ?? '',
2725 - 'mobileNumber' => $userinfo['phone_number'] ?? '',
2726 - 'phoneNumber' => $userinfo['phone_number'] ?? '',
2727 - 'userId' => $userinfo['phone_number'] ?? '',
2728 - 'sub' => $userinfo['sub']
2729 - );
2730 -
2731 - $result['userDetails'] = $userDetails;
2732 -
2733 - // We may have asked for the address of the customer, so add that too, or a dummy.
2734 - if (!isset($result['shippingDetails'])) {
2735 - $countries=new WC_Countries();
2736 - $address =[];
2737 - $address['addressLine1'] = "";
2738 - $address['addressLine2'] = "";
2739 - $address['city'] ="";
2740 - $address['postCode'] = "";
2741 - $address['country'] = $countries->get_base_country();
2742 -
2743 - // This uses other keys than both epayment and checkout, but we'll normalize it later. IOK 2025-08-13
2744 - if (isset($userinfo['address'])) {
2745 - $address['addressLine1'] = $userinfo['address']['street_address'];
2746 - $address['city'] = $userinfo['address']['region'];
2747 - $address['country'] = $userinfo['address']['country'];
2748 - $address['postCode'] = $userinfo['address']['postal_code'];
2749 - }
2750 - $result['shippingDetails'] = ['address' => $address];
2751 - }
2752 - }
2753 - }
2754 -
2755 2649 if ($ready && ($express || $checkout_session)) {
2756 - // For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
2757 - // This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12
2650 + // For Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
2651 + // This will also normalize userDetails, adding 'sub' where possible and fields for backwards compatibility. 2025-08-12
2758 2652 $result = $this->ensure_userDetails($result, $order);
2759 2653
2760 2654 // After, we need to normalize shipping details or even add them if e.g. using Checkout without address or contact info IOK 2025-08-13
2761 2655 // Epayment Express Checkout is of course also significantly different from both the old Express and from Checkout in the formatting here. IOK 2025-08-12
@@ -2827,9 +2721,9 @@
2827 2721 return $result;
2828 2722 }
2829 2723
2830 2724
2831 - // IOK 2024-01-09 If using Vipps Checkout with the BankTransfer method, which is eg. used in Finland,
2725 + // IOK 2024-01-09 If using Checkout with the BankTransfer method, which is eg. used in Finland,
2832 2726 // we are (currently) not receiving any 'state' or 'aggregate', so add this iff the payment is successful.
2833 2727 // The reason for this is that this payment type does not actually use the epayment API at all (!)
2834 2728 // Also moved some other compatibility code here -
2835 2729 // --- reference used to be orderId
@@ -2898,9 +2792,9 @@
2898 2792
2899 2793 return $result;
2900 2794 }
2901 2795
2902 - // Vipps Checkout v3 does *not* provide userDetails. Vipps Checkout v2 and epayment *does*. But Checkout additionally allows
2796 + // Checkout v3 does *not* provide userDetails. Checkout v2 and epayment *does*. But Checkout additionally allows
2903 2797 // for anonymous purchases, in which case there is *no* user details. In this case we provide an anonymous user so we can actually create an order.
2904 2798 // To handle this, we provide this utility that ensures we have userDetails no matter the input. For this we use the anonymous filters and "billingDetails" if present
2905 2799 // if not, we use shippingDetails. IOK 2023-01-10
2906 2800 // Also, epayment uses mobileNumber and checkout uses phoneNumber, so normalize.
@@ -2910,8 +2804,9 @@
2910 2804 // If we have userDetails, use it (ecom API with user data requested - Express Checkout
2911 2805 if (isset($vippsdata['userDetails'])) {
2912 2806 $userDetails = $vippsdata['userDetails'];
2913 2807 // This is the verified user information from the app - this is always the customer for Express Checkout, but not for Checkout IOK 2025-08-12
2808 + // Also, it may not always be available - it depends on consent and whether scope was added (probably) in epayment_initate_payment. IOK 2026-08-18
2914 2809 $sub = "";
2915 2810 if (isset($vippsdata['profile']) && isset($vippsdata['profile']['sub'])) {
2916 2811 $sub = $vippsdata['profile']['sub'];
2917 2812 }
@@ -3276,9 +3171,14 @@
3276 3171 $is_base64 = $shipping_table ? ( $shipping_table['_is_base64'] ?? false) : false;
3277 3172
3278 3173 if (is_array($shipping_table) && isset($shipping_table[$key])) {
3279 3174 $decoded = $is_base64 ? @base64_decode($shipping_table[$key]) : $shipping_table[$key];
3280 - $shipping_rate = $decoded ? @unserialize($decoded) : null;
3175 +
3176 + // Ensure no shop manager has injected an evil object (that they would have had to add as a plugin) here. IOK 2026-09-18
3177 + $allowed_classes = apply_filters('woo_vipps_express_checkout_allowed_shipping_classes', [WC_Shipping_Rate::class, \stdClass::class]);
3178 + $shipping_rate = $decoded ? @unserialize($decoded, ['allowed_classes' => $allowed_classes]) : null;
3179 + $shipping_rate = is_a($shipping_rate,'WC_Shipping_Rate') ? $shipping_rate : null;
3180 +
3281 3181 if (!$shipping_rate) {
3282 3182 $this->log(sprintf(__("%1\$s: Could not deserialize the chosen shipping method %2\$s for order %3\$d", 'woo-vipps'), Vipps::ExpressCheckoutName(), $method, $order->get_id()), 'error');
3283 3183 $this->log(sprintf(__("Serialized data was %1\$s", 'woo-vipps'), $decoded), 'error');
3284 3184 } else {
@@ -3298,9 +3198,9 @@
3298 3198 }
3299 3199 }
3300 3200 }
3301 3201
3302 - // Possible extra metadata from Vipps Checkout IOK 2023-01-17
3202 + // Possible extra metadata from Checkout IOK 2023-01-17
3303 3203 // Store in the order, but also in the shipping rate so it will be visible in the order screen
3304 3204 // along with the shipping ragte
3305 3205 if (isset($shipping['pickupPoint'])) {
3306 3206 $order->update_meta_data('vipps_checkout_pickupPoint', $shipping['pickupPoint']);
@@ -3360,9 +3260,9 @@
3360 3260 $methodclass = $methods_classes[$shipping_rate->get_method_id()] ?? null;
3361 3261 $shipping_method = $methodclass ? new $methodclass($shipping_rate->get_instance_id()) : null;
3362 3262 $is_vipps_checkout_shipping = $shipping_method && is_a($shipping_method, 'VippsCheckout_Shipping_Method');
3363 3263
3364 - // Some Vipps Checkout-specific shipping methods calculate the cost in the Vipps window.
3264 + // Some Checkout-specific shipping methods calculate the cost in the Vipps window.
3365 3265 if ($is_vipps_checkout_shipping && $shipping_method->dynamic_cost) {
3366 3266 $vippsamount = intval($order->get_meta('_vipps_amount'));
3367 3267 $shipping_tax_rate = floatval($order->get_meta('_vipps_shipping_tax_rates'));
3368 3268 $compareamount = $ordertotal * 100;
@@ -3398,9 +3298,9 @@
3398 3298
3399 3299 $order->set_total($ordertotal + $total_shipping + $total_shipping_tax);
3400 3300 $order->update_taxes(); // Necessary for the admin view only; does not recalculate order.
3401 3301
3402 - // Add an early hook for Vipps Checkout orders with special shipping methods
3302 + // Add an early hook for Checkout orders with special shipping methods
3403 3303 $metadata = $shipping_rate->get_meta_data();
3404 3304 if (isset($metadata['type'])) {
3405 3305 do_action('woo_vipps_checkout_special_shipping_method', $order, $shipping_rate, $metadata['type']);
3406 3306 }
@@ -3416,9 +3316,9 @@
3416 3316
3417 3317
3418 3318 // If we have the 'expresscreateuser' thing set to true, we will create or assign the order here, as it is the first-ish place where we can.
3419 3319 // If possible and safe, user will be logged in before being sent to the thankyou screen. IOK 2020-10-09
3420 - // Same thing for Vipps Checkout, mutatis mutandis. The function below returns false if no customer exists or gets created.
3320 + // Same thing for Checkout, mutatis mutandis. The function below returns false if no customer exists or gets created.
3421 3321 $customer = false;
3422 3322 if ($assigncustomer) {
3423 3323 $customer = Vipps::instance()->express_checkout_get_vipps_customer($order);
3424 3324 }
@@ -3426,8 +3326,9 @@
3426 3326 // This would have been used to ensure that we 'enroll' the users the same way as in the Login plugin. Unfortunately, the userId from express checkout isn't
3427 3327 // the same as the 'sub' we get in Login so that must be a future feature. IOK 2020-10-09
3428 3328 // IOK 2025-08-13 we do get the 'sub' now, at least for express checkout. For Checkout, we would have to compare the email of the user with the verified email
3429 3329 // after calling get_userinfo, so we'll leave that be.
3330 + // We *maybe* get the sub - it depends on consent, and *maybe* that a scope has been added in epayment_initate_payment. IOK 2026-08-18
3430 3331 if (class_exists('VippsWooLogin') && $customer && !is_wp_error($customer) && !get_user_meta($customer->get_id(), '_vipps_phone',true)) {
3431 3332 update_user_meta($customer->get_id(), '_vipps_phone', $billing['phoneNumber']);
3432 3333 if (isset($user['sub'])) {
3433 3334 $userid = $customer->get_id();
@@ -3461,9 +3362,9 @@
3461 3362 $shipping_rate = apply_filters('woo_vipps_express_checkout_shipping_rate',$shipping_rate,$costExTax,$tax,$method,$product);
3462 3363 return $shipping_rate;
3463 3364 }
3464 3365
3465 - // Used by both callback_check_order_status and handle_callback - sets the neccessary order metadata after a successful (or not vipps transaction). IOK 2025-08-13
3366 + // Used by both poll_and_check_order_status and handle_callback - sets the neccessary order metadata after a successful (or not vipps transaction). IOK 2025-08-13
3466 3367 public function order_set_transaction_metadata($order, $transaction) {
3467 3368 // Set Vipps metadata as early as possible
3468 3369 $vippsstamp = strtotime($transaction['timeStamp']);
3469 3370 $vippsamount = $transaction['amount'] ?? '';
@@ -3652,13 +3553,16 @@
3652 3553 $transaction['status'] = ($data['state'] ?? $details['state']);
3653 3554 $transaction['paymentmethod'] = $details['paymentMethod'] ?? "";
3654 3555 $this->order_set_transaction_metadata($order, $transaction);
3655 3556
3557 + // Dont do anything if order is not finalized. LP 2026-08-31
3558 + if (!in_array($newstatus, ['authorized', 'complete', 'cancelled'])) {
3559 + return;
3560 + }
3561 +
3656 3562 // This order is ready to set order shipping details etc for IOK 2025-09-19
3657 - $ready = false;
3658 - if (in_array($newstatus, ['authorized', 'complete'])) {
3659 - $ready = true;
3660 - }
3563 + $ready = in_array($newstatus, ['authorized', 'complete']);
3564 +
3661 3565 if ($ready) {
3662 3566 // Failsafe for rare bug when using Klarna Checkout with Vipps as an external payment method
3663 3567 // IOK 2024-01-09 ensure this is called only when order is complete/authorized
3664 3568 $this->reset_erroneous_payment_method($order);
@@ -3747,10 +3651,10 @@
3747 3651 // made to the session here will be saved. Sorry.
3748 3652 // UPDATE: Should be no more race condition since we moved callback into the action scheduler, and this shipping finalization into this rest endpoint. LP 2026-03-30
3749 3653 Vipps::instance()->callback_restore_session($order_id);
3750 3654
3751 - // For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
3752 - // This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12
3655 + // For Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
3656 + // This will also normalize userDetails, adding 'sub' where possible and fields for backwards compatibility. 2025-08-12
3753 3657 $data = $this->ensure_userDetails($data, $order);
3754 3658
3755 3659 // Some Express Checkout orders aren't really express checkout orders, but normal orders to which we have
3756 3660 // added scope name, email, phoneNumber. The reason is that we don't care about the address. But then
@@ -3840,9 +3744,9 @@
3840 3744 }
3841 3745 do_action('woo_vipps_payment_complete_at_shutdown', $order, $this);
3842 3746 } catch (Exception $e) {
3843 3747 // This is/should be non-critical so just log it.
3844 - $this->log(sprintf(__("Could not do all payment-complete actions on %1\$s order %2\$d: %3\$s ", 'woo-vipps'), Vipps::CompanyName(), $orderid, $e->etMessage()), "error");
3748 + $this->log(sprintf(__("Could not do all payment-complete actions on %1\$s order %2\$d: %3\$s ", 'woo-vipps'), Vipps::CompanyName(), $orderid, $e->getMessage()), "error");
3845 3749 }
3846 3750 }
3847 3751
3848 3752 // This is run on payment complete. Per default will it only add a link to the order confirmation page, but
@@ -3949,9 +3853,9 @@
3949 3853
3950 3854 $contents = WC()->cart->get_cart_contents();
3951 3855 $contents = apply_filters('woo_vipps_create_express_checkout_cart_contents',$contents);
3952 3856 try {
3953 - $cart_hash = md5(json_encode(wc_clean($contents)) . WC()->cart->total);
3857 + $cart_hash = WC()->cart->get_cart_hash();
3954 3858 $order = new WC_Order();
3955 3859 $order->set_status('pending');
3956 3860 $order->set_payment_method($this);
3957 3861 if ($ischeckout) {
@@ -3962,8 +3866,9 @@
3962 3866 }
3963 3867 // We use 'checkout' as the created_via key as per requests, but allow merchants to use their own. IOK 2022-09-15
3964 3868 $created_via = apply_filters('woo_vipps_express_checkout_created_via', 'checkout', $order, $ischeckout);
3965 3869 $order->set_created_via($created_via);
3870 + $order->set_cart_hash($cart_hash);
3966 3871
3967 3872 $dummy = sprintf(__('Vipps Express Checkout', 'woo-vipps')); // this is so gettext will find this string.
3968 3873 $dummy = sprintf(__('Vipps Checkout', 'woo-vipps')); // this is so gettext will find this string.
3969 3874
@@ -4125,9 +4030,9 @@
4125 4030 </p>
4126 4031 </div>
4127 4032 <?php endif; ?>
4128 4033
4129 - <?php // We will only show the Vipps Checkout options if the user has activated the feature (thus creating the pages involved etc). IOK 2021-10-01
4034 + <?php // We will only show the Checkout options if the user has activated the feature (thus creating the pages involved etc). IOK 2021-10-01
4130 4035 $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false);
4131 4036 ?>
4132 4037
4133 4038 <?php /* We will *not* allow vipps checkout to be activated at this point, since the product is no longer sold. IOK 2026-04-30 */ ?>
@@ -4188,8 +4093,23 @@
4188 4093 // If enabling this, ensure the page in question exists
4189 4094 if ($this->get_option('vipps_checkout_enabled') == 'yes') {
4190 4095 update_option('woo_vipps_checkout_activated', true, true); // This must be true here, but still, make sure
4191 4096 Vipps::instance()->maybe_create_vipps_pages();
4097 + }
4098 +
4099 + // Ensure special page has the necessary shortcode. LP 2026-09-01
4100 + $special_page = get_post(Vipps::get_special_page_id());
4101 + if ($special_page && !has_shortcode($special_page->post_content, 'vipps_special_page')) {
4102 + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->";
4103 + wp_update_post([
4104 + 'ID' => Vipps::get_special_page_id(),
4105 + 'post_content' => $new_content,
4106 + ]);
4107 + } else if (!Vipps::get_special_page_id()) {
4108 + // We shouldn't really get here, the page should be ensured to exist in init. LP 2026-09-03
4109 + /* translators: %s is current method name */
4110 + $this->log(sprintf(__('Missing special page in %s, attempting to fix', 'woo-vipps'), 'process_admin_options'), 'warning');
4111 + Vipps::instance()->ensure_special_page_exists();
4192 4112 }
4193 4113
4194 4114 return $saved;
4195 4115 }