PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.2.5
Pay with Vipps and MobilePay for WooCommerce v6.2.5
6.2.5 6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.10 6.1.9 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1.0 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 5.4.3 5.4.2 All 187 releases
← All changes | payment/WC_Gateway_Vipps.class.php +90 -151 6.1.0 → 6.2.5 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 && ! $has_billing_email ) {
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';
@@ -1084,8 +1070,9 @@
1084 1070 // New defaults based on old defaults
1085 1071 $default_static_shipping_for_checkout = 'no';
1086 1072 $default_ask_address_for_express = 'no';
1087 1073 $default_status_on_fail = 'failed';
1074 + $default_express_show_in_checkout = 'yes';
1088 1075 if ($current) {
1089 1076 $default_static_shipping_for_checkout = (isset($current['enablestaticshipping'])) ? $current['enablestaticshipping'] : 'no';
1090 1077 $default_ask_address_for_express = (isset($current['useExplicitCheckoutFlow']) && $current['useExplicitCheckoutFlow'] == "yes") ? "yes" : "no";
1091 1078 // The old default used the same value as for Express Checkout. IOK 2023-07-27
@@ -1241,9 +1228,9 @@
1241 1228 'description' => __('Your phone number where Porterbuddy may send you important messages. Format must be MSISDN (including country code). Example: "4791234567"','woo-vipps'),
1242 1229 'default' => '',
1243 1230 ),
1244 1231
1245 - // 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
1246 1233 'vcs_helthjem' => array(
1247 1234 'title' => __('Helthjem', 'woo-vipps'),
1248 1235 'label' => sprintf(__('Support Helthjem as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()),
1249 1236 'type' => 'checkbox',
@@ -1278,9 +1265,9 @@
1278 1265 ),
1279 1266
1280 1267 );
1281 1268
1282 - /* 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 */
1283 1270 $externals = [];
1284 1271 $external_payment_fields = [];
1285 1272 $allow_external_payments = $this->allow_external_payments_in_checkout();
1286 1273 if ($allow_external_payments) {
@@ -1603,22 +1590,24 @@
1603 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'),
1604 1591 ),
1605 1592
1606 1593 'vippsspecialpagetemplate' => array(
1607 - '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()),
1608 1595 'label' => sprintf(__('Use specific template for %1$s', 'woo-vipps'), Vipps::CompanyName()),
1609 1596 'type' => 'select',
1610 1597 'options' => $page_templates,
1611 - '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()),
1612 1599 'default' => ''),
1613 1600
1601 + // Deprecated, not shown anymore: TODO: remove this option in future. LP 2026-09-01
1614 1602 'vippsspecialpageid' => array(
1615 1603 'title' => sprintf(__('Use a real page ID for the special %1$s pages - neccessary for some themes', 'woo-vipps'), Vipps::CompanyName()),
1616 1604 'label' => __('Use a real page ID', 'woo-vipps'),
1617 1605 'type' => 'select',
1618 - 'options' => $page_list,
1606 + 'options' => [],
1619 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()),
1620 - 'default'=>''),
1608 + 'default' => ''
1609 + ),
1621 1610
1622 1611 'sendreceipts' => array(
1623 1612 'title' => __("Send receipts and order confirmation info to the customers' app on completed purchases.", 'woo-vipps'),
1624 1613 'label' => sprintf(__("Send receipts to the customers %1\$s app", 'woo-vipps'), Vipps::CompanyName()),
@@ -1782,9 +1771,9 @@
1782 1771 $ok = apply_filters('woo_vipps_is_available', $ok, $this);
1783 1772 return $ok;
1784 1773 }
1785 1774
1786 - // True if the alternative Vipps Checkout screen is both available and activated. Returns the page id of the checkout
1775 + // True if the alternative Checkout screen is both available and activated. Returns the page id of the checkout
1787 1776 // page for convenience. IOK 2021-10-01
1788 1777 public function vipps_checkout_available () {
1789 1778
1790 1779 if ($this->get_option('vipps_checkout_enabled') != 'yes') return false;
@@ -1855,9 +1844,8 @@
1855 1844 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');
1856 1845 return [];
1857 1846 }
1858 1847
1859 -
1860 1848 // From the request, get either [billing_phone] => or [vipps phone]
1861 1849 $phone = '';
1862 1850 if (isset($_POST['vippsphone'])) {
1863 1851 $phone = trim(sanitize_text_field($_POST['vippsphone']));
@@ -1950,9 +1938,8 @@
1950 1938 $limited_session = $this->generate_authtoken();
1951 1939 $returnurl = add_query_arg('ls',$limited_session,$returnurl);
1952 1940 $returnurl = add_query_arg('id', $order_id, $returnurl);
1953 1941
1954 -
1955 1942 try {
1956 1943 // If the order was 'failed', it isnt any more! yet!
1957 1944 if ($order->get_status() == 'failed') {
1958 1945 $order->set_status('pending', __('Setting order status to pending to start payment', 'woo-vipps'));
@@ -2000,12 +1987,14 @@
2000 1987 $order->update_meta_data('_vipps_init_timestamp',$vippstamp);
2001 1988 $order->update_meta_data('_vipps_orderurl', $url);
2002 1989
2003 1990 $order->update_meta_data('_vipps_status','INITIATE'); // INITIATE right now
2004 - $order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), $this->get_payment_method_name()));
2005 - $order->add_order_note(sprintf(__('Awaiting %1$s payment confirmation','woo-vipps'), $this->get_payment_method_name()));
1991 +
1992 + $name = $this->get_payment_method_name();
1993 + $order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), $name));
1994 + $order->add_order_note(sprintf(__('Awaiting %1$s payment confirmation','woo-vipps'),$name));
1995 +
2006 1996 $order->save();
2007 -
2008 1997 // Create a signal file that we can check without calling wordpress to see if our result is in IOK 2018-05-04
2009 1998 try {
2010 1999 $Vipps->createCallbackSignal($order);
2011 2000 } catch (Exception $e) {
@@ -2010,9 +1999,8 @@
2010 1999 $Vipps->createCallbackSignal($order);
2011 2000 } catch (Exception $e) {
2012 2001 // Could not create a signal file, but that's ok.
2013 2002 }
2014 -
2015 2003 do_action('woo_vipps_before_redirect_to_vipps',$order_id);
2016 2004
2017 2005 // This will send us to a receipt page where we will do the actual work. IOK 2018-04-20
2018 2006 return array('result'=>'success','redirect'=>$url);
@@ -2141,12 +2129,11 @@
2141 2129 if ($api == 'banktransfer') {
2142 2130 // This is an error - we should not ever get to the 'capture' branch if we are a banktransfer payment.
2143 2131 // IOK 2024-01-09
2144 2132 $content = [];
2145 - } elseif ($api == 'epayment') {
2133 + } else {
2134 + // Now the only other api is 'epayment' IOK 2026-08-18
2146 2135 $content = $this->api->epayment_capture_payment($order,$amount,$requestid);
2147 - } else {
2148 - $content = $this->api->capture_payment($order,$amount,$requestid);
2149 2136 }
2150 2137 } catch (TemporaryVippsApiException $e) {
2151 2138 $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');
2152 2139 $this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . "\n" . $e->getMessage());
@@ -2197,8 +2184,9 @@
2197 2184 return false;
2198 2185 }
2199 2186 // We'll use the same transaction id for all cancel jobs, as we can only do it completely. IOK 2018-05-07
2200 2187 // For epayment, partial cancellations will be possible. IOK 2022-11-12
2188 + // IOK 2026-08-18 actually, epayment does *not* support partial cancellation - all remaining funds are cancelled.
2201 2189 $api = $order->get_meta('_vipps_api');
2202 2190 try {
2203 2191 $requestid = "";
2204 2192 if ($api == 'banktransfer') {
@@ -2203,22 +2191,14 @@
2203 2191 $requestid = "";
2204 2192 if ($api == 'banktransfer') {
2205 2193 // If we are here, and the order is somehow not captured, just do nothing. IOK 2024-01-09
2206 2194 $content = [];
2207 - } elseif ($api == 'epayment') {
2195 + } else {
2196 + // api is here 'epayment'. IOK 2026-07-18
2208 2197 $requestid = 1;
2209 2198 // This will cancel any remaining, not-captured amount IOK 2026-01-28
2210 2199 $content = $this->api->epayment_cancel_payment($order,$requestid);
2211 - } else {
2212 - // If we have captured the order, we can't cancel it with the ecom API IOK 2018-05-07
2213 - $captured = intval($order->get_meta('_vipps_captured'));
2214 - if ($captured>0) {
2215 - $msg = sprintf(__('Cannot cancel a captured %1$s transaction - use refund instead', 'woo-vipps'), "ECOM " . $this->get_payment_method_name());
2216 - $this->adminerr($msg);
2217 - return false;
2218 - }
2219 - $content = $this->api->cancel_payment($order,$requestid);
2220 - }
2200 + }
2221 2201 } catch (TemporaryVippsApiException $e) {
2222 2202 $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');
2223 2203 $this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage());
2224 2204 return false;
@@ -2232,9 +2212,8 @@
2232 2212 // the epay v2 API would return transactionInfo and Summary with the result, the new epayment api returns nothing.
2233 2213 // Removed epay branch 2025-08-12 IOK
2234 2214 $total = intval($order->get_meta('_vipps_amount'));
2235 2215 $captured = intval($order->get_meta('_vipps_captured'));
2236 -# $cancelled = $amount + intval($order->get_meta('_vipps_cancelled');
2237 2216 $cancelled = $total;
2238 2217 $remaining = $total - $captured - $cancelled;
2239 2218
2240 2219 // We need to assume it worked. Also, we can't do partial cancels yet, so just cancel everything.
@@ -2282,13 +2261,12 @@
2282 2261 if ($api == 'banktransfer') {
2283 2262 $msg = sprintf(__("Cannot refund bank transfer order %1\$d", 'woo-vipps'), $order->get_id());
2284 2263 $this->log($msg, 'error');
2285 2264 throw new Exception($msg);
2286 - } elseif ($api == 'epayment') {
2265 + } else {
2266 + // api is now 'epayment' IOK 2026-08-18
2287 2267 $content = $this->api->epayment_refund_payment($order,$requestid,$amount,$cents);
2288 - } else {
2289 - $content = $this->api->refund_payment($order,$requestid,$amount,$cents);
2290 - }
2268 + }
2291 2269
2292 2270 $currency = $order->get_currency();
2293 2271
2294 2272 // Previously, we got updated transaction info in a transactionInfo field. this is no longer provided,
@@ -2472,10 +2450,8 @@
2472 2450 $order->update_meta_data('_vipps_status',$newvippsstatus);
2473 2451
2474 2452 // Extract order metadata from either Checkout or Epayment - set below IOK 2025-08-13
2475 2453 if (!empty($paymentdetails)) {
2476 -
2477 -
2478 2454 // checkout has a string, epayment has an array with upper case "type" and apparently, cardBin IOK 2025-08-12
2479 2455 $paymentMethod = $paymentdetails['paymentMethod'] ?? "epayment";
2480 2456 // After normalization, all APIs will have data here.
2481 2457 $details = $paymentdetails['paymentDetails'];
@@ -2688,73 +2664,11 @@
2688 2664 if (in_array($newstatus, ['authorized', 'complete'])) {
2689 2665 $ready = true;
2690 2666 }
2691 2667
2692 -
2693 - // 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.
2694 - // 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
2695 - // 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
2696 - // Also we don't get this when the state is different from AUTHORIZED. Especially not ABORTED.
2697 - // IOK 2025-09-29: This is *no longer the case* . We actually now get userDetails every time we add the relevant scopes,
2698 - // so this is now probably dead code.
2699 - if ($ready && $express && !$checkout_session && !isset($result['userDetails'])) {
2700 -
2701 - $sub = isset($result['profile']) && isset($result['profile']['sub']) ? $result['profile']['sub'] : null;
2702 - $userinfo = [];
2703 - if (!$sub) {
2704 - // This should never happen, but be prepared
2705 - $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" );
2706 - $order->add_order_note($message);
2707 - $this->log($message , "error");
2708 - } else {
2709 - // If this happens, the merchant *may* be able to retrieve the information from Vipps so add a note for it.
2710 - try {
2711 - $userinfo = $this->api->get_userinfo($sub);
2712 - } catch (Exception $e) {
2713 - $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());
2714 - $order->add_order_note($message);
2715 - $this->log($message, 'woo-vipps', "error");
2716 - }
2717 - }
2718 - if ($userinfo) {
2719 - $userDetails = array(
2720 - 'email_verified' => $userinfo['email_verified'],
2721 - 'email' => $userinfo['email'],
2722 - 'firstName' => $userinfo['given_name'] ?? '',
2723 - 'lastName' => $userinfo['family_name'] ?? '',
2724 - 'mobileNumber' => $userinfo['phone_number'] ?? '',
2725 - 'phoneNumber' => $userinfo['phone_number'] ?? '',
2726 - 'userId' => $userinfo['phone_number'] ?? '',
2727 - 'sub' => $userinfo['sub']
2728 - );
2729 -
2730 - $result['userDetails'] = $userDetails;
2731 -
2732 - // We may have asked for the address of the customer, so add that too, or a dummy.
2733 - if (!isset($result['shippingDetails'])) {
2734 - $countries=new WC_Countries();
2735 - $address =[];
2736 - $address['addressLine1'] = "";
2737 - $address['addressLine2'] = "";
2738 - $address['city'] ="";
2739 - $address['postCode'] = "";
2740 - $address['country'] = $countries->get_base_country();
2741 -
2742 - // This uses other keys than both epayment and checkout, but we'll normalize it later. IOK 2025-08-13
2743 - if (isset($userinfo['address'])) {
2744 - $address['addressLine1'] = $userinfo['address']['street_address'];
2745 - $address['city'] = $userinfo['address']['region'];
2746 - $address['country'] = $userinfo['address']['country'];
2747 - $address['postCode'] = $userinfo['address']['postal_code'];
2748 - }
2749 - $result['shippingDetails'] = ['address' => $address];
2750 - }
2751 - }
2752 - }
2753 -
2754 2668 if ($ready && ($express || $checkout_session)) {
2755 - // For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
2756 - // This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12
2669 + // For Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
2670 + // This will also normalize userDetails, adding 'sub' where possible and fields for backwards compatibility. 2025-08-12
2757 2671 $result = $this->ensure_userDetails($result, $order);
2758 2672
2759 2673 // 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
2760 2674 // 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
@@ -2826,9 +2740,9 @@
2826 2740 return $result;
2827 2741 }
2828 2742
2829 2743
2830 - // IOK 2024-01-09 If using Vipps Checkout with the BankTransfer method, which is eg. used in Finland,
2744 + // IOK 2024-01-09 If using Checkout with the BankTransfer method, which is eg. used in Finland,
2831 2745 // we are (currently) not receiving any 'state' or 'aggregate', so add this iff the payment is successful.
2832 2746 // The reason for this is that this payment type does not actually use the epayment API at all (!)
2833 2747 // Also moved some other compatibility code here -
2834 2748 // --- reference used to be orderId
@@ -2897,9 +2811,9 @@
2897 2811
2898 2812 return $result;
2899 2813 }
2900 2814
2901 - // Vipps Checkout v3 does *not* provide userDetails. Vipps Checkout v2 and epayment *does*. But Checkout additionally allows
2815 + // Checkout v3 does *not* provide userDetails. Checkout v2 and epayment *does*. But Checkout additionally allows
2902 2816 // 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.
2903 2817 // 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
2904 2818 // if not, we use shippingDetails. IOK 2023-01-10
2905 2819 // Also, epayment uses mobileNumber and checkout uses phoneNumber, so normalize.
@@ -2909,8 +2823,9 @@
2909 2823 // If we have userDetails, use it (ecom API with user data requested - Express Checkout
2910 2824 if (isset($vippsdata['userDetails'])) {
2911 2825 $userDetails = $vippsdata['userDetails'];
2912 2826 // 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
2827 + // 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
2913 2828 $sub = "";
2914 2829 if (isset($vippsdata['profile']) && isset($vippsdata['profile']['sub'])) {
2915 2830 $sub = $vippsdata['profile']['sub'];
2916 2831 }
@@ -3275,9 +3190,13 @@
3275 3190 $is_base64 = $shipping_table ? ( $shipping_table['_is_base64'] ?? false) : false;
3276 3191
3277 3192 if (is_array($shipping_table) && isset($shipping_table[$key])) {
3278 3193 $decoded = $is_base64 ? @base64_decode($shipping_table[$key]) : $shipping_table[$key];
3279 - $shipping_rate = $decoded ? @unserialize($decoded) : null;
3194 +
3195 + // Ensure no shop manager has injected an evil object (that they would have had to add as a plugin) here. IOK 2026-09-18
3196 + $shipping_rate = $decoded ? @unserialize($decoded, ['allowed_classes' => [WC_Shipping_Rate::class]]) : null;
3197 + $shipping_rate = is_a($shipping_rate,'WC_Shipping_Rate') ? $shipping_rate : null;
3198 +
3280 3199 if (!$shipping_rate) {
3281 3200 $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');
3282 3201 $this->log(sprintf(__("Serialized data was %1\$s", 'woo-vipps'), $decoded), 'error');
3283 3202 } else {
@@ -3297,9 +3216,9 @@
3297 3216 }
3298 3217 }
3299 3218 }
3300 3219
3301 - // Possible extra metadata from Vipps Checkout IOK 2023-01-17
3220 + // Possible extra metadata from Checkout IOK 2023-01-17
3302 3221 // Store in the order, but also in the shipping rate so it will be visible in the order screen
3303 3222 // along with the shipping ragte
3304 3223 if (isset($shipping['pickupPoint'])) {
3305 3224 $order->update_meta_data('vipps_checkout_pickupPoint', $shipping['pickupPoint']);
@@ -3359,9 +3278,9 @@
3359 3278 $methodclass = $methods_classes[$shipping_rate->get_method_id()] ?? null;
3360 3279 $shipping_method = $methodclass ? new $methodclass($shipping_rate->get_instance_id()) : null;
3361 3280 $is_vipps_checkout_shipping = $shipping_method && is_a($shipping_method, 'VippsCheckout_Shipping_Method');
3362 3281
3363 - // Some Vipps Checkout-specific shipping methods calculate the cost in the Vipps window.
3282 + // Some Checkout-specific shipping methods calculate the cost in the Vipps window.
3364 3283 if ($is_vipps_checkout_shipping && $shipping_method->dynamic_cost) {
3365 3284 $vippsamount = intval($order->get_meta('_vipps_amount'));
3366 3285 $shipping_tax_rate = floatval($order->get_meta('_vipps_shipping_tax_rates'));
3367 3286 $compareamount = $ordertotal * 100;
@@ -3397,9 +3316,9 @@
3397 3316
3398 3317 $order->set_total($ordertotal + $total_shipping + $total_shipping_tax);
3399 3318 $order->update_taxes(); // Necessary for the admin view only; does not recalculate order.
3400 3319
3401 - // Add an early hook for Vipps Checkout orders with special shipping methods
3320 + // Add an early hook for Checkout orders with special shipping methods
3402 3321 $metadata = $shipping_rate->get_meta_data();
3403 3322 if (isset($metadata['type'])) {
3404 3323 do_action('woo_vipps_checkout_special_shipping_method', $order, $shipping_rate, $metadata['type']);
3405 3324 }
@@ -3415,9 +3334,9 @@
3415 3334
3416 3335
3417 3336 // 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.
3418 3337 // If possible and safe, user will be logged in before being sent to the thankyou screen. IOK 2020-10-09
3419 - // Same thing for Vipps Checkout, mutatis mutandis. The function below returns false if no customer exists or gets created.
3338 + // Same thing for Checkout, mutatis mutandis. The function below returns false if no customer exists or gets created.
3420 3339 $customer = false;
3421 3340 if ($assigncustomer) {
3422 3341 $customer = Vipps::instance()->express_checkout_get_vipps_customer($order);
3423 3342 }
@@ -3425,8 +3344,9 @@
3425 3344 // 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
3426 3345 // the same as the 'sub' we get in Login so that must be a future feature. IOK 2020-10-09
3427 3346 // 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
3428 3347 // after calling get_userinfo, so we'll leave that be.
3348 + // 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
3429 3349 if (class_exists('VippsWooLogin') && $customer && !is_wp_error($customer) && !get_user_meta($customer->get_id(), '_vipps_phone',true)) {
3430 3350 update_user_meta($customer->get_id(), '_vipps_phone', $billing['phoneNumber']);
3431 3351 if (isset($user['sub'])) {
3432 3352 $userid = $customer->get_id();
@@ -3651,13 +3571,16 @@
3651 3571 $transaction['status'] = ($data['state'] ?? $details['state']);
3652 3572 $transaction['paymentmethod'] = $details['paymentMethod'] ?? "";
3653 3573 $this->order_set_transaction_metadata($order, $transaction);
3654 3574
3575 + // Dont do anything if order is not finalized. LP 2026-08-31
3576 + if (!in_array($newstatus, ['authorized', 'complete', 'cancelled'])) {
3577 + return;
3578 + }
3579 +
3655 3580 // This order is ready to set order shipping details etc for IOK 2025-09-19
3656 - $ready = false;
3657 - if (in_array($newstatus, ['authorized', 'complete'])) {
3658 - $ready = true;
3659 - }
3581 + $ready = in_array($newstatus, ['authorized', 'complete']);
3582 +
3660 3583 if ($ready) {
3661 3584 // Failsafe for rare bug when using Klarna Checkout with Vipps as an external payment method
3662 3585 // IOK 2024-01-09 ensure this is called only when order is complete/authorized
3663 3586 $this->reset_erroneous_payment_method($order);
@@ -3746,10 +3669,10 @@
3746 3669 // made to the session here will be saved. Sorry.
3747 3670 // 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
3748 3671 Vipps::instance()->callback_restore_session($order_id);
3749 3672
3750 - // For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
3751 - // This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12
3673 + // For Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10
3674 + // This will also normalize userDetails, adding 'sub' where possible and fields for backwards compatibility. 2025-08-12
3752 3675 $data = $this->ensure_userDetails($data, $order);
3753 3676
3754 3677 // Some Express Checkout orders aren't really express checkout orders, but normal orders to which we have
3755 3678 // added scope name, email, phoneNumber. The reason is that we don't care about the address. But then
@@ -3839,9 +3762,9 @@
3839 3762 }
3840 3763 do_action('woo_vipps_payment_complete_at_shutdown', $order, $this);
3841 3764 } catch (Exception $e) {
3842 3765 // This is/should be non-critical so just log it.
3843 - $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");
3766 + $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");
3844 3767 }
3845 3768 }
3846 3769
3847 3770 // This is run on payment complete. Per default will it only add a link to the order confirmation page, but
@@ -3948,9 +3871,9 @@
3948 3871
3949 3872 $contents = WC()->cart->get_cart_contents();
3950 3873 $contents = apply_filters('woo_vipps_create_express_checkout_cart_contents',$contents);
3951 3874 try {
3952 - $cart_hash = md5(json_encode(wc_clean($contents)) . WC()->cart->total);
3875 + $cart_hash = WC()->cart->get_cart_hash();
3953 3876 $order = new WC_Order();
3954 3877 $order->set_status('pending');
3955 3878 $order->set_payment_method($this);
3956 3879 if ($ischeckout) {
@@ -3961,8 +3884,9 @@
3961 3884 }
3962 3885 // We use 'checkout' as the created_via key as per requests, but allow merchants to use their own. IOK 2022-09-15
3963 3886 $created_via = apply_filters('woo_vipps_express_checkout_created_via', 'checkout', $order, $ischeckout);
3964 3887 $order->set_created_via($created_via);
3888 + $order->set_cart_hash($cart_hash);
3965 3889
3966 3890 $dummy = sprintf(__('Vipps Express Checkout', 'woo-vipps')); // this is so gettext will find this string.
3967 3891 $dummy = sprintf(__('Vipps Checkout', 'woo-vipps')); // this is so gettext will find this string.
3968 3892
@@ -4124,9 +4048,9 @@
4124 4048 </p>
4125 4049 </div>
4126 4050 <?php endif; ?>
4127 4051
4128 - <?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
4052 + <?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
4129 4053 $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false);
4130 4054 ?>
4131 4055
4132 4056 <?php /* We will *not* allow vipps checkout to be activated at this point, since the product is no longer sold. IOK 2026-04-30 */ ?>
@@ -4187,8 +4111,23 @@
4187 4111 // If enabling this, ensure the page in question exists
4188 4112 if ($this->get_option('vipps_checkout_enabled') == 'yes') {
4189 4113 update_option('woo_vipps_checkout_activated', true, true); // This must be true here, but still, make sure
4190 4114 Vipps::instance()->maybe_create_vipps_pages();
4115 + }
4116 +
4117 + // Ensure special page has the necessary shortcode. LP 2026-09-01
4118 + $special_page = get_post(Vipps::get_special_page_id());
4119 + if ($special_page && !has_shortcode($special_page->post_content, 'vipps_special_page')) {
4120 + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->";
4121 + wp_update_post([
4122 + 'ID' => Vipps::get_special_page_id(),
4123 + 'post_content' => $new_content,
4124 + ]);
4125 + } else if (!Vipps::get_special_page_id()) {
4126 + // We shouldn't really get here, the page should be ensured to exist in init. LP 2026-09-03
4127 + /* translators: %s is current method name */
4128 + $this->log(sprintf(__('Missing special page in %s, attempting to fix', 'woo-vipps'), 'process_admin_options'), 'warning');
4129 + Vipps::instance()->ensure_special_page_exists();
4191 4130 }
4192 4131
4193 4132 return $saved;
4194 4133 }