| 1 |
<?php |
| 2 |
/* |
| 3 |
This class is for hooks and plugin managent for the Vipps Checkout checkout-replacement feature, and is instantiated as a singleton. |
| 4 |
IOK 2023-05-16 |
| 5 |
For WP-specific interactions. |
| 6 |
|
| 7 |
|
| 8 |
This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce |
| 9 |
Copyright (c) 2023 WP-Hosting AS |
| 10 |
|
| 11 |
MIT License |
| 12 |
|
| 13 |
Copyright (c) 2023 WP-Hosting AS |
| 14 |
|
| 15 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
| 16 |
of this software and associated documentation files (the "Software"), to deal |
| 17 |
in the Software without restriction, including without limitation the rights |
| 18 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 19 |
copies of the Software, and to permit persons to whom the Software is |
| 20 |
furnished to do so, subject to the following conditions: |
| 21 |
|
| 22 |
The above copyright notice and this permission notice shall be included in all |
| 23 |
copies or substantial portions of the Software. |
| 24 |
|
| 25 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 26 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 27 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 28 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 29 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 30 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 31 |
SOFTWARE. |
| 32 |
|
| 33 |
|
| 34 |
*/ |
| 35 |
if ( ! defined( 'ABSPATH' ) ) { |
| 36 |
exit; // Exit if accessed directly |
| 37 |
} |
| 38 |
|
| 39 |
class VippsCheckout { |
| 40 |
private static $instance = null; |
| 41 |
private $gw = null; |
| 42 |
private $payid = 0; // Used to improve handling of "what is the current checkout page" IOK 2024-11-14 |
| 43 |
|
| 44 |
public static function instance() { |
| 45 |
if (!static::$instance) static::$instance = new VippsCheckout(); |
| 46 |
return static::$instance; |
| 47 |
} |
| 48 |
|
| 49 |
public function gateway() { |
| 50 |
if ($this->gw) return $this->gw; |
| 51 |
$this->gw = Vipps::instance()->gateway(); |
| 52 |
return $this->gw; |
| 53 |
} |
| 54 |
|
| 55 |
public static function register_hooks() { |
| 56 |
$VippsCheckout = static::instance(); |
| 57 |
if (is_admin()) { |
| 58 |
add_action('admin_init',array($VippsCheckout,'admin_init')); |
| 59 |
} |
| 60 |
add_action('init',array($VippsCheckout,'init')); |
| 61 |
add_action( 'woocommerce_loaded', array($VippsCheckout,'woocommerce_loaded')); |
| 62 |
add_action( 'template_redirect', array($VippsCheckout,'template_redirect')); |
| 63 |
add_action( 'admin_post_nopriv_vipps_gw', array($VippsCheckout, 'choose_other_gw')); |
| 64 |
add_action( 'admin_post_vipps_gw', array($VippsCheckout, 'choose_other_gw')); |
| 65 |
add_filter( 'woocommerce_order_email_verification_required', array($VippsCheckout, 'allow_other_payment_method_email'), 10, 3); |
| 66 |
add_action('wp_footer', array($VippsCheckout, 'maybe_proceed_to_payment')); |
| 67 |
|
| 68 |
// Expire the Checkout session on order cancel. LP 2025-09-25 |
| 69 |
add_action('woocommerce_order_status_cancelled', array($VippsCheckout, 'woocommerce_order_status_cancelled')); |
| 70 |
|
| 71 |
|
| 72 |
add_filter('woo_vipps_shipping_method_pickup_points', function ($points, $rate, $shipping_method, $order) { |
| 73 |
if ($rate->method_id == 'pickup_location' && $order->get_meta('_vipps_checkout')) { |
| 74 |
// $locations = $shipping_method->pickup_locations ; // Protected attribute. Could wrap, but life is short so |
| 75 |
$locations = get_option( $shipping_method->id . '_pickup_locations', [] ); |
| 76 |
foreach ( $locations as $index => $location ) { |
| 77 |
if ( ! $location['enabled'] ) { |
| 78 |
continue; |
| 79 |
} |
| 80 |
|
| 81 |
$addr = $location['address'] ?? []; |
| 82 |
$default_country = WC()->countries->get_base_country() ?: "NO"; |
| 83 |
|
| 84 |
$point = []; |
| 85 |
$point['id'] = "$index"; |
| 86 |
$point['name'] = $location['name'] ?: " "; |
| 87 |
$point['address'] = $addr['address_1'] ?: " "; |
| 88 |
$point['postalCode'] = $addr['postcode'] ?: " "; |
| 89 |
$point['city'] = $addr['city'] ?: " "; |
| 90 |
$point['country'] = $addr['country'] ?: $default_country; |
| 91 |
$points[] = $point; |
| 92 |
} |
| 93 |
} |
| 94 |
return $points; |
| 95 |
}, 10, 4); |
| 96 |
|
| 97 |
|
| 98 |
} |
| 99 |
|
| 100 |
function woocommerce_order_status_cancelled ($orderid) { |
| 101 |
$order = wc_get_order($orderid); |
| 102 |
// Do this only if we have a checkout saved in the order |
| 103 |
$is_checkout = $order->get_meta('_vipps_checkout_session'); |
| 104 |
if (!$is_checkout) return; |
| 105 |
try { |
| 106 |
// If the order isn't in the right state, this will do nothing. IOK 2025-10-08 |
| 107 |
$this->gateway()->api->checkout_expire_session($order); |
| 108 |
} catch (Exception $e) { |
| 109 |
$this->log(sprintf(__("Cannot expire checkout session for order %d: %s", 'woo-vipps'), $orderid, $e->getMessage())); |
| 110 |
} |
| 111 |
// Cleanup the order, and ensure that we don't do this twice by deleting the old session |
| 112 |
$order->delete_meta_data('_vipps_checkout_session'); |
| 113 |
$order->save_meta_data(); |
| 114 |
} |
| 115 |
|
| 116 |
public function allow_other_payment_method_email ($email_verification_required, $order, $context ) { |
| 117 |
if (is_checkout_pay_page()) { |
| 118 |
$proceed = $order->get_meta('_vc_proceed'); |
| 119 |
if ($proceed) { |
| 120 |
// Maybe do a timestamp check here FIXME |
| 121 |
return false; |
| 122 |
} |
| 123 |
} |
| 124 |
return $email_verification_required; |
| 125 |
} |
| 126 |
|
| 127 |
# For "Choose other payment method" in Vipps Checkout, we will decorate the order with the chosen gw |
| 128 |
# and use javascript to go directly to that method. This is partly because we can't set default gateway to |
| 129 |
# kco on the order pay screen. IOK 2024-05-15 |
| 130 |
public function maybe_proceed_to_payment() { |
| 131 |
if (is_checkout_pay_page()) { |
| 132 |
$orderid = absint(get_query_var( 'order-pay')); |
| 133 |
$order = $orderid ? wc_get_order($orderid) : null; |
| 134 |
$gateway = sanitize_title(trim(is_a($order, 'WC_Order') ? $order->get_meta('_vc_proceed') : false)); |
| 135 |
$method = $gateway; |
| 136 |
// Choose invoice payment if Klarna payments is the gateway. IOK 2024-10-11 |
| 137 |
if ($gateway == 'klarna_payments') { |
| 138 |
$method = 'klarna_payments_pay_later'; |
| 139 |
} |
| 140 |
$method = apply_filters('woo_vipps_checkout_external_payment_method_selected', $method, $gateway, $order); |
| 141 |
$order->update_meta_data('_vc_proceed', 'any'); // Just in case we return to the order-pay page |
| 142 |
$order->save(); |
| 143 |
|
| 144 |
if ($method != 'any'): |
| 145 |
?> |
| 146 |
<script> |
| 147 |
jQuery(document).ready(function () { |
| 148 |
let selected = jQuery('input#payment_method_<?php echo sanitize_title($method); ?>[name="payment_method"]'); |
| 149 |
if (selected.length > 0) { |
| 150 |
jQuery('input#terms[type="checkbox"]').prop('checked', true).trigger('change'); |
| 151 |
selected.prop('checked', true).trigger('change'); |
| 152 |
// Neccessary for Klarna Payments |
| 153 |
setTimeout(function () { |
| 154 |
jQuery('button#place_order').click(); }, 100); |
| 155 |
} |
| 156 |
}); |
| 157 |
</script> |
| 158 |
<?php |
| 159 |
endif; |
| 160 |
} |
| 161 |
} |
| 162 |
|
| 163 |
// Returns list of external payment methods - from Vipps id to gateway id. |
| 164 |
// IOK 2024-10-11 filterable by 'woo_vipps_checkout_external_payment_methods' |
| 165 |
public function external_payment_methods() { |
| 166 |
$available = array_keys(WC()->payment_gateways->get_available_payment_gateways()); |
| 167 |
$gw = WC_Gateway_Vipps::instance(); |
| 168 |
$ok = $gw->allow_external_payments_in_checkout(); |
| 169 |
if (!$ok) return []; |
| 170 |
// Only defined value at this point - klarna means either of these gateways (IOK 2024-05-28) |
| 171 |
// Prioritize payments if present |
| 172 |
$possible = ['klarna' => ['klarna_payments', 'kco']]; |
| 173 |
$externals = []; |
| 174 |
foreach ($possible as $key => $gws) { |
| 175 |
$on = $gw->get_option('checkout_external_payments_' . $key); |
| 176 |
$active = array_intersect($gws, $available); |
| 177 |
if ($on == "yes" && !empty($active)) { |
| 178 |
$externals[$key] = ['gw' => array_values($active)[0]]; |
| 179 |
} |
| 180 |
} |
| 181 |
return $externals; |
| 182 |
} |
| 183 |
|
| 184 |
# Called in admin-post and will finalize a Vipps Checkout order + send the customer to the payment page. |
| 185 |
public function choose_other_gw () { |
| 186 |
$orderid = intval($_GET['o']); |
| 187 |
$gw = trim(sanitize_title($_GET['gw'])); |
| 188 |
if ($gw == 'any') $gw = ""; |
| 189 |
$nonce = $_GET['cb']; |
| 190 |
$ok = wp_verify_nonce($nonce, 'vipps_gw'); |
| 191 |
if (!$ok) { |
| 192 |
$this->abandonVippsCheckoutOrder(false); |
| 193 |
$this->log(sprintf(__("Orderid %1\$s: Wrong nonce when trying to switch payment methods.", 'woo-vipps'), $orderid), 'error'); |
| 194 |
wp_redirect(home_url()); |
| 195 |
} |
| 196 |
$order = wc_get_order($orderid); |
| 197 |
if (!$order || $order->get_status() != 'pending') { |
| 198 |
$this->abandonVippsCheckoutOrder(false); |
| 199 |
$this->log(sprintf(__("Orderid %1\$s is not pending when choosing another payment method from Vipps Checkout", 'woo-vipps'), $orderid), 'error'); |
| 200 |
wp_redirect(home_url()); |
| 201 |
} |
| 202 |
|
| 203 |
try { |
| 204 |
// Load session from cookies - it will not get loaded on admin-post. |
| 205 |
WC()->initialize_session(); |
| 206 |
|
| 207 |
if (WC()->session) { |
| 208 |
if (! WC()->session->has_session()) { |
| 209 |
WC()->session->set_customer_session_cookie( true ); |
| 210 |
} |
| 211 |
// There is actually a bug here for KCO which will redirect to the normal checkout page with an error message. |
| 212 |
// Try to stop that.. IOK 2024-05-15 |
| 213 |
if ($gw != 'kco') { |
| 214 |
WC()->session->set('chosen_payment_method', $gw); |
| 215 |
} |
| 216 |
$addressdata = []; |
| 217 |
$addressdata["billing_email"] = $order->get_billing_email(); |
| 218 |
$addressdata["billing_address_1"] = $order->get_billing_address_1(); |
| 219 |
$addressdata["billing_address_2"] = $order->get_billing_address_2(); |
| 220 |
$addressdata["billing_postcode"] = $order->get_billing_postcode(); |
| 221 |
$addressdata["billing_city"] = $order->get_billing_city(); |
| 222 |
$addressdata["billing_country"] = $order->get_billing_country(); |
| 223 |
$addressdata["billing_first_name"] = $order->get_billing_first_name(); |
| 224 |
$addressdata["billing_last_name"] = $order->get_billing_last_name(); |
| 225 |
$addressdata["billing_phone"] = $order->get_billing_phone(); |
| 226 |
$addressdata["billing_city"] = $order->get_billing_city(); |
| 227 |
$addressdata["shipping_address_1"] = $order->get_shipping_address_1(); |
| 228 |
$addressdata["shipping_address_2"] = $order->get_shipping_address_2(); |
| 229 |
$addressdata["shipping_city"] = $order->get_shipping_city(); |
| 230 |
$addressdata["shipping_postcode"] = $order->get_shipping_postcode(); |
| 231 |
$addressdata["shipping_country"] = $order->get_shipping_country(); |
| 232 |
$addressdata["shipping_first_name"] = $order->get_shipping_first_name(); |
| 233 |
$addressdata["shipping_last_name"] = $order->get_shipping_last_name(); |
| 234 |
$addressdata["shipping_phone"] = $order->get_shipping_phone(); |
| 235 |
$addressdata["shipping_city"] = $order->get_shipping_city(); |
| 236 |
WC()->session->set('vc_address', $addressdata); |
| 237 |
WC()->session->save_data(); |
| 238 |
} else { |
| 239 |
$this->log(__("No session choosing other gateway from Vipps Checkout", 'woo-vipps'), 'error'); |
| 240 |
} |
| 241 |
|
| 242 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 243 |
|
| 244 |
// If we got here, we actually have shipping information already in place, so we can continue with the order directly! |
| 245 |
$paymentdetails = WC_Gateway_Vipps::instance()->get_payment_details($order); |
| 246 |
// As of writing, userDetails is not included in the return. LP 2026-03-13 |
| 247 |
$paymentdetails = WC_Gateway_Vipps::instance()->ensure_userDetails($paymentdetails, $order); |
| 248 |
|
| 249 |
// Unless the order actually somehow doesn't exist anymore - this should not be possible but let's handle it |
| 250 |
if ($paymentdetails && isset($paymentdetails['status']) && $paymentdetails['status'] == 'CANCEL') { |
| 251 |
// IOK this cannot actually happen, but if it *does*, cancel the order |
| 252 |
clean_post_cache($order->get_id()); |
| 253 |
if (WC()->session) { |
| 254 |
WC()->session->set('vipps_checkout_current_pending',0); |
| 255 |
WC()->session->set('vipps_address_hash', false); |
| 256 |
} |
| 257 |
$order->set_status('cancelled', __("Session terminated with no payment", 'woo-vipps'), false); |
| 258 |
// Also mark for deletion and remove stored session |
| 259 |
$order->update_meta_data('_vipps_delendum',1); |
| 260 |
$order->save(); |
| 261 |
$url = $order->get_checkout_payment_url(); |
| 262 |
wp_redirect($url); |
| 263 |
exit(); |
| 264 |
} |
| 265 |
|
| 266 |
$billing = isset($paymentdetails['billingDetails']) ? $paymentdetails['billingDetails'] : false; |
| 267 |
# Don't assign the order to its user if we are not logged in - we are not completing this order using Vipps IOK 2024-05-15 |
| 268 |
$assignuser = is_user_logged_in(); |
| 269 |
|
| 270 |
WC_Gateway_Vipps::instance()->set_order_shipping_details($order,($paymentdetails['shippingDetails'] ?? []), $paymentdetails['userDetails'], $billing, $paymentdetails, $assignuser); |
| 271 |
|
| 272 |
# Now reset payment gateway and clear out the VC session |
| 273 |
$order->set_payment_method($gw); |
| 274 |
$order->update_meta_data('_vc_proceed', ($gw ? $gw : 'any')); |
| 275 |
$order->add_order_note(sprintf(__('Alternative payment method "%1$s" chosen, customer returned from Checkout', 'woo-vipps'), $gw)); |
| 276 |
$order->save(); |
| 277 |
// Should not be neccessary but we'll add this just so any caching does not cause other systems to return the wrong data here IOK 2025-03-18 |
| 278 |
clean_post_cache($order->get_id()); |
| 279 |
|
| 280 |
$url = $order->get_checkout_payment_url(); |
| 281 |
|
| 282 |
// This makes sure there is no "current vipps checkout" order, as this order is no longer payable at Vipps. |
| 283 |
// Actually, don't do this because it will most likely just create more spurious orders while this remains unpayable. IOK 2024-06-04 |
| 284 |
// $this->abandonVippsCheckoutOrder(false); |
| 285 |
wp_redirect($url); |
| 286 |
exit(); |
| 287 |
} catch (Exception $e) { |
| 288 |
$msg = sprintf(__("Could not select other payment gateway for order %1\$s", 'woo-vipps'), $order->get_id()); |
| 289 |
$this->log($msg, 'error'); |
| 290 |
$order->set_status('cancelled', $msg, false); |
| 291 |
// Also mark for deletion and remove stored session |
| 292 |
$order->update_meta_data('_vipps_delendum',1); |
| 293 |
$order->save(); |
| 294 |
$this->abandonVippsCheckoutOrder(false); |
| 295 |
$url = $order->get_checkout_payment_url(); |
| 296 |
wp_redirect($url); |
| 297 |
exit(); |
| 298 |
} |
| 299 |
} |
| 300 |
|
| 301 |
public function template_redirect () { |
| 302 |
// This will normally be on the "checkout" page which shouldn't be cached, but just in case, add |
| 303 |
// nocache headres to any page that uses this shortcode. IOK 2021-08-26 |
| 304 |
// Furthermore, sometimes woocommerce calls is_checkout() *before* woocommerce is loaded, so |
| 305 |
global $post; |
| 306 |
|
| 307 |
// Modify cart coupon validation - has to be very early because otherwise we can't show |
| 308 |
// notices in gutenberg cart IOK 2025-05-15 |
| 309 |
$this->handle_coupon_invalidation_in_checkout(); |
| 310 |
|
| 311 |
if ($post && is_page() && has_shortcode($post->post_content, 'vipps_checkout')) { |
| 312 |
// Add fonts for the widgets on this page IOK 2025-05-02 |
| 313 |
wp_enqueue_style('vipps-fonts',plugins_url('css/fonts.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/fonts.css"), 'all'); |
| 314 |
|
| 315 |
add_filter('woocommerce_is_checkout', '__return_true'); |
| 316 |
add_filter('body_class', function ($classes) { |
| 317 |
$classes[] = 'vipps-checkout'; |
| 318 |
$classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| 319 |
return apply_filters('woo_vipps_checkout_body_class', $classes); |
| 320 |
}); |
| 321 |
/* Suppress the title for this page, but on the front page only IOK 2023-01-27 (by request from Vipps) */ |
| 322 |
$post_to_hide_title_for = $post->ID; |
| 323 |
add_filter('the_title', function ($title, $postid = 0) use ($post_to_hide_title_for) { |
| 324 |
if (!is_admin() && $postid == $post_to_hide_title_for && is_singular() && in_the_loop()) { |
| 325 |
$title = ""; |
| 326 |
} |
| 327 |
return $title; |
| 328 |
}, 10, 2); |
| 329 |
Vipps::nocache(); |
| 330 |
} |
| 331 |
} |
| 332 |
|
| 333 |
|
| 334 |
public function init () { |
| 335 |
add_action('wp_loaded', array($this, 'wp_register_scripts')); |
| 336 |
|
| 337 |
// For Vipps Checkout, poll for the result of the current session |
| 338 |
add_action('wp_ajax_vipps_checkout_poll_session', array($this, 'vipps_ajax_checkout_poll_session')); |
| 339 |
add_action('wp_ajax_nopriv_vipps_checkout_poll_session', array($this, 'vipps_ajax_checkout_poll_session')); |
| 340 |
// Use ajax to initiate the session too |
| 341 |
add_action('wp_ajax_vipps_checkout_start_session', array($this, 'vipps_ajax_checkout_start_session')); |
| 342 |
add_action('wp_ajax_nopriv_vipps_checkout_start_session', array($this, 'vipps_ajax_checkout_start_session')); |
| 343 |
|
| 344 |
// And for user-defined callbacks in the widgets. These may modify the order. |
| 345 |
add_action('wp_ajax_vipps_checkout_callback', array($this, 'vipps_ajax_checkout_callback')); |
| 346 |
add_action('wp_ajax_nopriv_vipps_checkout_callback', array($this, 'vipps_ajax_checkout_callback')); |
| 347 |
|
| 348 |
// Check cart total before initiating Vipps Checkout NT-2024-09-07 |
| 349 |
// This allows for real-time validation of the cart before proceeding with the checkout process |
| 350 |
add_action('wp_ajax_vipps_checkout_validate_cart', array($this, 'ajax_vipps_checkout_validate_cart')); |
| 351 |
add_action('wp_ajax_nopriv_vipps_checkout_validate_cart', array($this, 'ajax_vipps_checkout_validate_cart')); |
| 352 |
|
| 353 |
// Retrieve widgets - this is done by ajax so as to ensure that the Order object exists at this point. |
| 354 |
add_action('wp_ajax_vipps_checkout_get_widgets', array($this, 'vipps_ajax_get_widgets')); |
| 355 |
add_action('wp_ajax_nopriv_vipps_checkout_get_widgets', array($this, 'vipps_ajax_get_widgets')); |
| 356 |
|
| 357 |
// Prevent previews and prefetches of the Vipps Checkout page starting and creating orders |
| 358 |
add_action('wp_head', array($this, 'wp_head')); |
| 359 |
|
| 360 |
|
| 361 |
// The Vipps Checkout feature which overrides the normal checkout process uses a shortcode |
| 362 |
add_shortcode('vipps_checkout', array($this, 'vipps_checkout_shortcode')); |
| 363 |
|
| 364 |
// Ensure we remove the current session on the thank you page (too). |
| 365 |
add_action('woocommerce_thankyou_vipps', function () { |
| 366 |
WC()->session->set('vipps_checkout_current_pending',false); |
| 367 |
WC()->session->set('vipps_address_hash', false); |
| 368 |
WC()->session->set('vipps_shipping_rate_id_map', false); |
| 369 |
}); |
| 370 |
|
| 371 |
// For Vipps Checkout - we need to know any time and as soon as the cart changes, so fold all the events into a single one. IOK 2021-08-24 |
| 372 |
add_action( 'woocommerce_add_to_cart', function ($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { |
| 373 |
do_action('vipps_cart_changed'); |
| 374 |
}, 10, 6); |
| 375 |
// Cart emptied |
| 376 |
add_action( 'woocommerce_cart_emptied', function ($clear_persistent_cart) { |
| 377 |
do_action('vipps_cart_changed'); |
| 378 |
}, 10, 1); |
| 379 |
// After updating quantities |
| 380 |
add_action('woocommerce_after_cart_item_quantity_update', function ( $cart_item_key, $quantity, $old_quantity ) { |
| 381 |
do_action('vipps_cart_changed'); |
| 382 |
}, 10, 3); |
| 383 |
// Blocks and ajax |
| 384 |
add_action( 'woocommerce_cart_item_removed', function ($cart_item_key, $cart) { |
| 385 |
do_action('vipps_cart_changed'); |
| 386 |
}, 10, 3); |
| 387 |
// Restore deleted entry |
| 388 |
add_action( 'woocommerce_cart_item_restored', function ($cart_item_key, $cart) { |
| 389 |
do_action('vipps_cart_changed'); |
| 390 |
}, 10, 3); |
| 391 |
// Normal cart form update |
| 392 |
add_filter('woocommerce_update_cart_action_cart_updated', function ($updated) { |
| 393 |
do_action('vipps_cart_changed'); |
| 394 |
return $updated; |
| 395 |
}); |
| 396 |
// Trigger cart_changed when a coupon is applied |
| 397 |
add_action('woocommerce_applied_coupon', array($this, 'cart_changed')); |
| 398 |
// Trigger cart_changed when a coupon is removed |
| 399 |
add_action('woocommerce_removed_coupon', array($this, 'cart_changed')); |
| 400 |
// Then handle the actual cart change |
| 401 |
add_action('vipps_cart_changed', array($this, 'cart_changed')); |
| 402 |
} |
| 403 |
|
| 404 |
public function admin_init () { |
| 405 |
// Stuff for the special Vipps Checkout page |
| 406 |
add_filter('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages'), 10, 1); |
| 407 |
} |
| 408 |
|
| 409 |
// Extra stuff in the <head>, which will mostly mean dynamic CSS |
| 410 |
public function wp_head () { |
| 411 |
// If we have a Vipps Checkout page, stop iOS from giving previews of it that |
| 412 |
// starts the session - iOS should use the visibility API of the browser for this, but it doesn't as of 2021-11-11 |
| 413 |
$checkoutid = wc_get_page_id('vipps_checkout'); |
| 414 |
if ($checkoutid) { |
| 415 |
$url = get_permalink($checkoutid); |
| 416 |
echo "<style> a[href=\"$url\"] { -webkit-touch-callout: none; } </style>\n"; |
| 417 |
} |
| 418 |
} |
| 419 |
|
| 420 |
public function wp_register_scripts () { |
| 421 |
$sdkurl = 'https://checkout.vipps.no/vippsCheckoutSDK.js'; |
| 422 |
wp_register_script('vipps-sdk',$sdkurl,array()); |
| 423 |
wp_register_script('vipps-checkout-widgets', plugins_url('js/vipps-checkout-widgets.js', __FILE__), [], filemtime(dirname(__FILE__) . "/js/vipps-checkout-widgets.js"), 'true'); |
| 424 |
wp_register_script('vipps-checkout',plugins_url('js/vipps-checkout.js',__FILE__),array('vipps-gw','vipps-sdk', 'vipps-checkout-widgets'),filemtime(dirname(__FILE__) . "/js/vipps-checkout.js"), 'true'); |
| 425 |
} |
| 426 |
|
| 427 |
public function log ($what,$type='info') { |
| 428 |
$logger = function_exists('wc_get_logger') ? wc_get_logger() : false; |
| 429 |
if ($logger) { |
| 430 |
$context = array('source'=>'woo-vipps'); |
| 431 |
$logger->log($type,$what,$context); |
| 432 |
} else { |
| 433 |
error_log("woo-vipps ($type): $what"); |
| 434 |
} |
| 435 |
} |
| 436 |
|
| 437 |
|
| 438 |
// This is used by the Vipps Checkout page to start the Vipps checkout session, including |
| 439 |
// creating the partial order IOK 2021-09-03 |
| 440 |
// IOK FIXME: Add support for starting payment *for a given order*. 2023-08-15 |
| 441 |
public function vipps_ajax_checkout_start_session () { |
| 442 |
check_ajax_referer('do_vipps_checkout','vipps_checkout_sec'); |
| 443 |
$url = ""; |
| 444 |
$redir = ""; |
| 445 |
$token = ""; |
| 446 |
|
| 447 |
Vipps::set_locale_if_in_header(); |
| 448 |
|
| 449 |
// First, check that we haven't already done this like in another window or something: |
| 450 |
// IOK 2024-06-04 This also happens when using the back button! Sometimes! |
| 451 |
$sessioninfo = $this->vipps_checkout_current_pending_session(); |
| 452 |
|
| 453 |
if (isset($sessioninfo['redirect'])) { |
| 454 |
$redirect = $sessioninfo['redirect']; |
| 455 |
} |
| 456 |
if (isset($sessioninfo['session']) && isset($sessioninfo['session']['token'])) { |
| 457 |
$token = $sessioninfo['session']['token']; |
| 458 |
$src = $sessioninfo['session']['checkoutFrontendUrl']; |
| 459 |
$url = $src; |
| 460 |
} |
| 461 |
|
| 462 |
// And if we do, just return what we have. NB: This *should not happen*. |
| 463 |
// IOK 2025-05-04 what are you talking about IOK, this absolutely happens e.g. when using the backbutton to a page starting the orders. |
| 464 |
if ($url || $redir) { |
| 465 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 466 |
return wp_send_json_success(array('ok'=>1, 'msg'=>'session started', 'src'=>$url, 'redirect'=>$redir, 'token'=>$token, 'orderid'=>$current_pending)); |
| 467 |
} |
| 468 |
|
| 469 |
// Otherwise, create an order and start a new session |
| 470 |
$session = null; |
| 471 |
$current_pending = 0; |
| 472 |
$current_authtoken = ""; |
| 473 |
$limited_session = ""; |
| 474 |
try { |
| 475 |
$current_pending = $this->gateway()->create_partial_order('ischeckout'); |
| 476 |
if ($current_pending) { |
| 477 |
$order = wc_get_order($current_pending); |
| 478 |
$order->update_meta_data('_vipps_checkout', true); |
| 479 |
$current_authtoken = $this->gateway()->generate_authtoken(); |
| 480 |
$limited_session = $this->gateway()->generate_authtoken(); |
| 481 |
$order->update_meta_data('_vipps_authtoken',wp_hash_password($current_authtoken)); |
| 482 |
$order->update_meta_data('_vipps_limited_session',wp_hash_password($limited_session)); |
| 483 |
$order->save(); |
| 484 |
WC()->session->set('vipps_checkout_current_pending', $current_pending); |
| 485 |
|
| 486 |
try { |
| 487 |
Vipps::instance()->maybe_add_static_shipping($this->gateway(),$order->get_id(), 'vippscheckout'); |
| 488 |
} catch (Exception $e) { |
| 489 |
// In this case, we just have to continue. |
| 490 |
$this->log(sprintf(__("Error calculating static shipping for order %1\$s", 'woo-vipps'), $order->get_id()), 'error'); |
| 491 |
$this->log($e->getMessage(),'error'); |
| 492 |
} |
| 493 |
$this->gateway()->save_session_in_order($order); |
| 494 |
do_action('woo_vipps_checkout_order_created', $order); |
| 495 |
} else { |
| 496 |
throw new Exception(sprintf(__('Unknown error creating %1$s partial order', 'woo-vipps'), Vipps::CheckoutName())); |
| 497 |
} |
| 498 |
} catch (Exception $e) { |
| 499 |
return wp_send_json_success(array('ok'=>0, 'msg'=>$e->getMessage(), 'src'=>'', 'redirect'=>'', 'orderid'=>0)); |
| 500 |
} |
| 501 |
|
| 502 |
// Ensure we get the latest updates to the order too IOK 2021-10-22 |
| 503 |
$order = wc_get_order($current_pending); |
| 504 |
if (is_user_logged_in()) { |
| 505 |
$phone = get_user_meta(get_current_user_id(), 'billing_phone', true); |
| 506 |
} |
| 507 |
$order_id = $order->get_id(); |
| 508 |
$returnurl = Vipps::instance()->payment_return_url(); |
| 509 |
$returnurl = add_query_arg('ls',$limited_session,$returnurl); |
| 510 |
$returnurl = add_query_arg('id', $order_id, $returnurl); |
| 511 |
|
| 512 |
$sessionorders= WC()->session->get('_vipps_session_orders'); |
| 513 |
if (!$sessionorders) $sessionorders = array(); |
| 514 |
$sessionorders[$order_id] = 1; |
| 515 |
WC()->session->set('_vipps_pending_order',$order_id); |
| 516 |
WC()->session->set('_vipps_session_orders',$sessionorders); |
| 517 |
|
| 518 |
$customer_id = get_current_user_id(); |
| 519 |
if ($customer_id) { |
| 520 |
$customer = new WC_Customer( $customer_id ); |
| 521 |
} else { |
| 522 |
$customer = WC()->customer; |
| 523 |
} |
| 524 |
|
| 525 |
if ($customer) { |
| 526 |
$customerinfo['email'] = $customer->get_billing_email(); |
| 527 |
$customerinfo['firstName'] = $customer->get_billing_first_name(); |
| 528 |
$customerinfo['lastName'] = $customer->get_billing_last_name(); |
| 529 |
$customerinfo['streetAddress'] = $customer->get_billing_address_1(); |
| 530 |
$address2 = trim($customer->get_billing_address_2()); |
| 531 |
if (!empty($address2)) { |
| 532 |
$customerinfo['streetAddress'] = $customerinfo['streetAddress'] . ", " . $address2; |
| 533 |
} |
| 534 |
$customerinfo['city'] = $customer->get_billing_city(); |
| 535 |
$customerinfo['postalCode'] = $customer->get_billing_postcode(); |
| 536 |
$customerinfo['country'] = $customer->get_billing_country(); |
| 537 |
|
| 538 |
// Currently Vipps requires all phone numbers to have area codes and NOT the +-sign prefix, nor 00. |
| 539 |
// We can't guaratee that at all, but try for Norway |
| 540 |
$customerinfo['phoneNumber'] = ""; |
| 541 |
$phonenr = Vipps::normalizePhoneNumber($customer->get_billing_phone(), $customerinfo['country']); |
| 542 |
if ($phonenr) { |
| 543 |
$customerinfo['phoneNumber'] = $phonenr; |
| 544 |
} |
| 545 |
} |
| 546 |
|
| 547 |
$customerinfo = apply_filters('woo_vipps_customerinfo', $customerinfo, $order); |
| 548 |
|
| 549 |
try { |
| 550 |
$session = $this->gateway()->api->initiate_checkout($customerinfo,$order,$returnurl,$current_authtoken); |
| 551 |
if ($session) { |
| 552 |
$order = wc_get_order($current_pending); |
| 553 |
$order->update_meta_data('_vipps_init_timestamp',time()); |
| 554 |
$order->update_meta_data('_vipps_status','INITIATE'); |
| 555 |
$order->update_meta_data('_vipps_checkout_session', $session); |
| 556 |
|
| 557 |
$order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), Vipps::CheckoutName())); |
| 558 |
$order->add_order_note(sprintf(__('Customer passed to %1$s','woo-vipps'), Vipps::CheckoutName())); |
| 559 |
$order->save(); |
| 560 |
$token = $session['token']; |
| 561 |
$src = $session['checkoutFrontendUrl']; |
| 562 |
$url = $src; |
| 563 |
} else { |
| 564 |
throw new Exception(sprintf(__('Unknown error creating %1$s session', 'woo-vipps'), Vipps::CheckoutName())); |
| 565 |
} |
| 566 |
} catch (Exception $e) { |
| 567 |
$this->log(sprintf(__("Could not initiate %1\$s session: %2\$s", 'woo-vipps'), Vipps::CheckoutName(), $e->getMessage()), 'ERROR'); |
| 568 |
return wp_send_json_success(array('ok'=>0, 'msg'=>$e->getMessage(), 'src'=>'', 'redirect'=>'', 'orderid'=>$order_id)); |
| 569 |
} |
| 570 |
if ($url || $redir) { |
| 571 |
return wp_send_json_success(array('ok'=>1, 'msg'=>'session started', 'src'=>$url, 'redirect'=>$redir, 'token'=>$token, 'orderid'=>$order_id)); |
| 572 |
} else { |
| 573 |
return wp_send_json_success(array('ok'=>0, 'msg'=>sprintf(__('Could not start %1$s session'), Vipps::CheckoutName()),'src'=>$url, 'redirect'=>$redir, 'orderid'=>$order_id)); |
| 574 |
} |
| 575 |
} |
| 576 |
|
| 577 |
// Handler function for all other callbacks from the Vipps MobilePay checkout screen - adding |
| 578 |
// coupons, modifying the order etc. Actions are added with the filter 'woo_vipps_checkout_callback_actions' IOK 2025-05-13 |
| 579 |
// -- they are functions taking the action name and an order object. IOK 2025-05-13 |
| 580 |
public function vipps_ajax_checkout_callback() { |
| 581 |
check_ajax_referer('do_vipps_checkout','vipps_checkout_sec'); |
| 582 |
$orderid = intval($_REQUEST['orderid']??0); // Currently not used because we are using a single pending order in session |
| 583 |
$lock_held = intval($_REQUEST['lock_held'] ?? 0); |
| 584 |
$action = sanitize_title($_REQUEST['callback_action'] ?? 0); |
| 585 |
|
| 586 |
Vipps::set_locale_if_in_header(); |
| 587 |
|
| 588 |
add_filter('woo_vipps_is_checkout_callback', '__return_true'); // Signal that this is a special context. |
| 589 |
|
| 590 |
// add some default action handlers IOK 2025-05-13 |
| 591 |
$this->add_widget_callback_actions(); |
| 592 |
|
| 593 |
$actions = apply_filters('woo_vipps_checkout_callback_actions', []); |
| 594 |
|
| 595 |
$handler = $actions[$action] ?? false; |
| 596 |
if (!$handler) { |
| 597 |
$msg = sprintf(__("Vipps MobilePay Checkout callback with unknown action: %s", 'woo-vipps'), $action); |
| 598 |
$this->log($msg, 'DEBUG'); |
| 599 |
return wp_send_json_error(array('msg'=>'FAILED', 'error'=>$msg)); |
| 600 |
} |
| 601 |
// The single current pending order. IOK 2025-04-25 |
| 602 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 603 |
$order = $current_pending ? wc_get_order($current_pending) : null; |
| 604 |
if (!$order) { |
| 605 |
return wp_send_json_error(array('msg'=>'FAILED', 'error'=>'Unknown order')); |
| 606 |
} |
| 607 |
$prevtotal = $order->get_total() ?: 0; |
| 608 |
try { |
| 609 |
$result = $handler($action, $order); |
| 610 |
$order = wc_get_order($order->get_id()); // Incase the order has changed since last wc_get_order. LP 2025-05-14 |
| 611 |
$newtotal = $order->get_total() ?: 0; |
| 612 |
|
| 613 |
if ($lock_held) { |
| 614 |
// If the order total has changed, we may want to recalculate shipping methods. |
| 615 |
list($new_shipping, $old_table) = $this->maybe_recalc_shipping_methods($order, $prevtotal, $newtotal); |
| 616 |
if ($new_shipping || $newtotal != $prevtotal) { |
| 617 |
try { |
| 618 |
$res = $this->gateway()->api->checkout_modify_session($order, $new_shipping); |
| 619 |
} catch (Exception $e) { |
| 620 |
$this->log(__("Problem modifying Checkout session: ", 'woo-vipps') . $e->getMessage()); |
| 621 |
if ($new_shipping) { |
| 622 |
$order->update_meta_data('_vipps_express_checkout_shipping_method_table', $old_table); |
| 623 |
$order->save_meta_data(); |
| 624 |
} |
| 625 |
} |
| 626 |
} |
| 627 |
} |
| 628 |
return wp_send_json_success(array('msg'=>$result)); |
| 629 |
} catch (Exception $e) { |
| 630 |
return wp_send_json_error(array('msg'=>'FAILED', 'error'=>$e->getMessage())); |
| 631 |
} |
| 632 |
} |
| 633 |
|
| 634 |
// We call this in the *modify* branch of Vipps Checkout if the customer adds a coupon or changes the value |
| 635 |
// of the order so that free shipping may be added or removed. IOK 2025-09-16 |
| 636 |
// On successful return, we will return both the new set of shipping rates and the old table so any errors can be reverted. |
| 637 |
private function maybe_recalc_shipping_methods ($order, $old_price, $new_price) { |
| 638 |
// We will only recalculate if we already have a shipping table; and because the "modify" call may fail, |
| 639 |
// we'll return the previous table so that we can then revert to the previous table. IOK 2025-09-16 |
| 640 |
$existing_table = $order->get_meta('_vipps_express_checkout_shipping_method_table'); |
| 641 |
if (empty($existing_table)) return [false, false]; |
| 642 |
|
| 643 |
// This is any shipping method with zero cost which is *not* local pickup. We are not going to try to find "free shipping" rates or methods |
| 644 |
// using load_shipping_methods or anything like that, because any shipping method can implement free shipping and we can't know which |
| 645 |
// until we recalculate shipping for the package, which could be costly. Instead we'll look at the price of the order and any coupons that have been added. |
| 646 |
// IOK 2025-09-16 |
| 647 |
$had_free_shipping = ($existing_table['_meta_has_free_shipping'] ?? false); |
| 648 |
|
| 649 |
// We *should* recalc primarily if we did have free shipping, but the new price of the order is lower than the old one, |
| 650 |
// or if we did *not* have free shipping but the new price is *larger*. This is because free shipping is typically linked to |
| 651 |
// the cart value. IOK 2025-09-16 |
| 652 |
$should_recalc = ($had_free_shipping && ($old_price > $new_price)) || (!$had_free_shipping && ($old_price < $new_price)); |
| 653 |
// Then we run a filter on this so that forexample adding or removing a free-shipping coupont will have the neccessary effect. IOK 2025-09-16 |
| 654 |
$should_recalc = apply_filters('woo_vipps_checkout_recalculate_shipping', $should_recalc, $had_free_shipping, $order, $old_price); |
| 655 |
|
| 656 |
if (!$should_recalc) return [false, false]; |
| 657 |
|
| 658 |
$vippsorderid = $order->get_meta('_vipps_orderid'); |
| 659 |
$new_return = Vipps::instance()->vipps_shipping_details_callback_handler($order, [],$vippsorderid, 'ischeckout'); |
| 660 |
$new_return = $new_return['shippingDetails']; |
| 661 |
|
| 662 |
return [$new_return, $existing_table]; |
| 663 |
} |
| 664 |
|
| 665 |
// Check the current status of the current Checkout session for the user. |
| 666 |
public function vipps_ajax_checkout_poll_session () { |
| 667 |
check_ajax_referer('do_vipps_checkout','vipps_checkout_sec'); |
| 668 |
|
| 669 |
$orderid = intval($_REQUEST['orderid']??0); // Currently not used because we are using a single pending order in session |
| 670 |
$lock_held = intval($_REQUEST['lock_held'] ?? 0); |
| 671 |
$type = $_REQUEST['type'] ?? "unknown"; // Type of callback |
| 672 |
$polldata = $_REQUEST['pollData'] ?? []; // the actual Checkout js event data. LP 2026-03-19 |
| 673 |
|
| 674 |
Vipps::set_locale_if_in_header(); |
| 675 |
|
| 676 |
// The single current pending order. IOK 2025-04-25 |
| 677 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 678 |
$order = $current_pending ? wc_get_order($current_pending) : null; |
| 679 |
|
| 680 |
$payment_status = $order ? $this->gateway()->check_payment_status($order) : 'unknown'; |
| 681 |
if (in_array($payment_status, ['authorized', 'complete'])) { |
| 682 |
$this->abandonVippsCheckoutOrder(false); |
| 683 |
return wp_send_json_success(array('msg'=>'completed', 'url' => $this->gateway()->get_return_url($order)));; |
| 684 |
} |
| 685 |
if ($payment_status == 'cancelled') { |
| 686 |
$this->log(sprintf(__("%1\$s session %2\$d cancelled (payment status)", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id()), 'debug'); |
| 687 |
$this->abandonVippsCheckoutOrder($order); |
| 688 |
return wp_send_json_error(array('msg'=>'FAILED', 'url'=>home_url())); |
| 689 |
} |
| 690 |
|
| 691 |
$session = $order ? $order->get_meta('_vipps_checkout_session') : false; |
| 692 |
if (!$session) { |
| 693 |
WC()->session->set('vipps_address_hash', false); |
| 694 |
return wp_send_json_success(array('msg'=>'EXPIRED', 'url'=>false)); |
| 695 |
} |
| 696 |
|
| 697 |
add_filter('woo_vipps_is_vipps_checkout', '__return_true'); |
| 698 |
$status = $this->get_vipps_checkout_status($order); |
| 699 |
|
| 700 |
$failed = $status == 'ERROR' || $status == 'EXPIRED' || $status == 'TERMINATED'; |
| 701 |
|
| 702 |
// Disallow sessions that go on for too long. |
| 703 |
if (is_a($order, "WC_Order")) { |
| 704 |
$created = $order->get_date_created(); |
| 705 |
$timestamp = 0; |
| 706 |
$now = time(); |
| 707 |
try { |
| 708 |
$timestamp = $created->getTimestamp(); |
| 709 |
} catch (Exception $e) { |
| 710 |
// PHP 8 gives ValueError for certain older versions of WooCommerce here. |
| 711 |
$timestamp = intval($created->format('U')); |
| 712 |
|
| 713 |
} |
| 714 |
$passed = $now - $timestamp; |
| 715 |
$minutes = ($passed / 60); |
| 716 |
// Expire after 50 minutes |
| 717 |
if ($minutes > 50) { |
| 718 |
$this->log(sprintf(__("%1\$s session %2\$d expired after %3\$d minutes (limit 50)", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id(), $minutes), 'debug'); |
| 719 |
$this->abandonVippsCheckoutOrder($order); |
| 720 |
return wp_send_json_success(array('msg'=>'EXPIRED', 'url'=>false)); |
| 721 |
} |
| 722 |
} |
| 723 |
|
| 724 |
$ok = !$failed; |
| 725 |
|
| 726 |
// Since we checked the payment status at Vipps directly above, we don't actaully have any extra information at this point. |
| 727 |
// We do know that the session is live and ongoing, but that's it. |
| 728 |
|
| 729 |
if ($failed) { |
| 730 |
$msg = $status; |
| 731 |
$this->log(sprintf(__("%1\$s session %2\$d failed with message %3\$s", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id(), $msg), 'debug'); |
| 732 |
$this->abandonVippsCheckoutOrder($order); |
| 733 |
return wp_send_json_error(array('msg'=>$msg, 'url'=>home_url())); |
| 734 |
exit(); |
| 735 |
} |
| 736 |
// Errorhandling! If this happens we have an unknown status or something like it. |
| 737 |
if (!$ok) { |
| 738 |
$this->log("Unknown status on polling status: " . print_r($status, true), 'ERROR'); |
| 739 |
$this->abandonVippsCheckoutOrder($order); |
| 740 |
return wp_send_json_error(array('msg'=>'ERROR', 'url'=>false)); |
| 741 |
exit(); |
| 742 |
} |
| 743 |
|
| 744 |
// This handles address information data from the poll if present. It is not, currently. 2021-09-27 IOK |
| 745 |
// it is now! IOK 2024-04-24 |
| 746 |
$change = false; |
| 747 |
$vipps_address_hash = WC()->session->get('vipps_address_hash'); |
| 748 |
if ($ok && (isset($status['billingDetails']) || isset($status['shippingDetails']))) { |
| 749 |
$serialized = sha1(json_encode(@$status['billingDetails']) . ':' . json_encode(@$status['shippingDetails'])); |
| 750 |
if ($serialized != $vipps_address_hash) { |
| 751 |
$change = true; |
| 752 |
WC()->session->set('vipps_address_hash', $serialized); |
| 753 |
} |
| 754 |
} |
| 755 |
|
| 756 |
// IOK This is the actual status of the order when this is called, which will |
| 757 |
// include personalia only when available |
| 758 |
if ($ok && $change && isset($status['billingDetails'])) { |
| 759 |
$contact = $status['billingDetails']; |
| 760 |
$order->set_billing_email($contact['email']); |
| 761 |
$order->set_billing_phone($contact['phoneNumber']); |
| 762 |
$order->set_billing_first_name($contact['firstName']); |
| 763 |
$order->set_billing_last_name($contact['lastName']); |
| 764 |
$order->set_billing_address_1($contact['streetAddress'] ?? ""); |
| 765 |
$order->set_billing_city($contact['city'] ?? ""); |
| 766 |
$order->set_billing_postcode($contact['postalCode'] ?? ""); |
| 767 |
$order->set_billing_country($contact['country'] ?? ""); |
| 768 |
} |
| 769 |
if ($ok && $change && isset($status['shippingDetails'])) { |
| 770 |
$contact = $status['shippingDetails']; |
| 771 |
if ($contact['country'] ?? false) { |
| 772 |
$countrycode = Vipps::instance()->country_to_code($contact['country']); // No longer neccessary IOK 2023-01-09 |
| 773 |
$order->set_shipping_first_name($contact['firstName']); |
| 774 |
$order->set_shipping_last_name($contact['lastName']); |
| 775 |
$order->set_shipping_address_1($contact['streetAddress']); |
| 776 |
$order->set_shipping_city($contact['city']); |
| 777 |
$order->set_shipping_postcode($contact['postalCode']); |
| 778 |
$order->set_shipping_country($contact['country']); |
| 779 |
} |
| 780 |
|
| 781 |
} |
| 782 |
if ($change) { |
| 783 |
$order->save(); |
| 784 |
} |
| 785 |
|
| 786 |
// When the address changes, the VAT/taxes may have changed too. Recalculate the order total if we know the Vipps lock |
| 787 |
// of the order is held. IOK 2025-04-25 |
| 788 |
if ($change) { |
| 789 |
$prevtotal = $order->get_total() ?: 0; |
| 790 |
$newtotal = $order->calculate_totals(true); // With taxes please |
| 791 |
if ($lock_held && $newtotal != $prevtotal) { |
| 792 |
try { |
| 793 |
$res = $this->gateway()->api->checkout_modify_session($order); |
| 794 |
$order->save(); |
| 795 |
} catch (Exception $e) { |
| 796 |
$this->log(__("Problem modifying Checkout session: ", 'woo-vipps') . $e->getMessage()); |
| 797 |
if ($newtotal < $prevtotal) { |
| 798 |
// In this case, the orders value will be lower than what is reserved at Vipps, which is OK - the rest will be cancelled |
| 799 |
// on order completion. IOK 2025-05-24 |
| 800 |
$order->save(); |
| 801 |
} |
| 802 |
} |
| 803 |
} |
| 804 |
} |
| 805 |
|
| 806 |
switch ($type) { |
| 807 |
// Store the newly selected shipping method in session. LP 2026-03-19 |
| 808 |
// NB: selecting a new pickup location option inside the same pickup location group does NOT trigger an event here. LP 2026-03-19 |
| 809 |
case 'shipping_selected': |
| 810 |
if (!is_a(WC()->session, 'WC_Session') || !is_a($order, 'WC_Order')) { |
| 811 |
/* translators: order id */ |
| 812 |
$this->gw->log(sprintf(__('Checkout ajax new shipping: session not ok for order %1$s.', 'woo-vipps'), $order->get_id()), 'error'); |
| 813 |
break; |
| 814 |
} |
| 815 |
|
| 816 |
$new_shipping_key = $polldata['id'] ?? null; // our custom key like '$flat_rate$a1vkbszd221:index' used in shipping table, NOT the same as woo rate_id. LP 2026-03-19 |
| 817 |
if (!$new_shipping_key) { |
| 818 |
break; |
| 819 |
} |
| 820 |
|
| 821 |
// Retrieve the woo rate id for new shipping selection, we have stored this in the session. LP 2026-03-20 |
| 822 |
$shipping_rate_id_map = WC()->session->get('vipps_shipping_rate_id_map'); |
| 823 |
$new_shipping_rate_id = $shipping_rate_id_map[$new_shipping_key] ?? null; |
| 824 |
|
| 825 |
if (!$new_shipping_rate_id) { |
| 826 |
/* translators: order id, shipping rate key */ |
| 827 |
$this->gw->log(sprintf(__('Checkout ajax new shipping: did not find shipping rate id for the new shipping choice "%2$s" order %1$s.', 'woo-vipps'), $order->get_id(), $new_shipping_key), 'error'); |
| 828 |
break; |
| 829 |
} |
| 830 |
|
| 831 |
$change = true; |
| 832 |
WC()->session->set('chosen_shipping_methods', [$new_shipping_rate_id]); |
| 833 |
WC()->session->save_data(); |
| 834 |
break; |
| 835 |
} |
| 836 |
|
| 837 |
if ($ok && $change) { |
| 838 |
wp_send_json_success(array('msg'=>'order_change', 'url'=>'')); |
| 839 |
exit(); |
| 840 |
} |
| 841 |
if ($ok) { |
| 842 |
wp_send_json_success(array('msg'=>'no_change', 'url'=>'')); |
| 843 |
exit(); |
| 844 |
} |
| 845 |
|
| 846 |
// This should never happen. |
| 847 |
wp_send_json_success(array('msg'=>'unknown', 'url'=>'')); |
| 848 |
} |
| 849 |
|
| 850 |
// Check cart total before initiating Vipps Checkout NT-2024-09-07 |
| 851 |
// Also any other checks we might want to do in the future. This will validate the cart each time the |
| 852 |
// checkout page loads, even if a session is already in progress. IOK 2024-09-09 |
| 853 |
public function ajax_vipps_checkout_validate_cart() { |
| 854 |
Vipps::set_locale_if_in_header(); |
| 855 |
$cart_total = WC()->cart->get_total('edit') ?: 0; |
| 856 |
$minimum_amount = 1; // 1 in the store currency |
| 857 |
|
| 858 |
if ($cart_total < $minimum_amount) { |
| 859 |
wp_send_json_error(array( |
| 860 |
'message' => sprintf(__('Vipps Checkout cannot be used for orders less than %1$s %2$s', 'woo-vipps'), $minimum_amount, get_woocommerce_currency() ) |
| 861 |
)); |
| 862 |
} else { |
| 863 |
wp_send_json_success(array('message', __("OK", 'woo-vipps'))); |
| 864 |
} |
| 865 |
} |
| 866 |
|
| 867 |
// Retrieve the current pending Vipps Checkout session, if it exists, and do some cleanup |
| 868 |
// if it isn't correct IOK 2021-09-03 |
| 869 |
protected function vipps_checkout_current_pending_session () { |
| 870 |
// If this is set, this is a currently pending order which is maybe still valid |
| 871 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 872 |
$order = $current_pending ? wc_get_order($current_pending) : null; |
| 873 |
|
| 874 |
# If we do have an order, we need to check if it is 'pending', and if not, we have to check its payment status |
| 875 |
$payment_status = null; |
| 876 |
if ($order) { |
| 877 |
if ($order->get_status() == 'pending') { |
| 878 |
$payment_status = 'initiated'; // Just assume this for now |
| 879 |
} else { |
| 880 |
$payment_status = $order ? $this->gateway()->check_payment_status($order) : 'unknown'; |
| 881 |
} |
| 882 |
} |
| 883 |
// This covers situations where we can actually go directly to the thankyou-screen or whatever |
| 884 |
$redirect = ""; |
| 885 |
if (in_array($payment_status, ['authorized', 'complete'])) { |
| 886 |
$this->abandonVippsCheckoutOrder(false); |
| 887 |
$redirect = $this->gateway()->get_return_url($order); |
| 888 |
} elseif ($payment_status == 'cancelled') { |
| 889 |
$this->log(sprintf(__("%1\$s session %2\$d cancelled (pending session)", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id()), 'debug'); |
| 890 |
// This will mostly just wipe the session. |
| 891 |
$this->abandonVippsCheckoutOrder($order); |
| 892 |
// Previously we redirected to home_page() here, but in this case with a cancelled session, |
| 893 |
// we want to keep the user at the Checkout and start a new session instead. LP 2026-03-17 |
| 894 |
} |
| 895 |
// Now if we don't have an order right now, we should not have a session either, so fix that |
| 896 |
if (!$order) { |
| 897 |
$this->abandonVippsCheckoutOrder(false); |
| 898 |
} |
| 899 |
|
| 900 |
// Now check the orders vipps session if it exist |
| 901 |
$session = $order ? $order->get_meta('_vipps_checkout_session') : false; |
| 902 |
|
| 903 |
// A single word or array containing session data, containing token and frontendFrameUrl |
| 904 |
// If a word, it will be ERROR EXPIRED FAILED IOK 2025-04-07 |
| 905 |
$session_status = $session ? $this->get_vipps_checkout_status($order) : null; |
| 906 |
|
| 907 |
// If this is the case, there is no redirect, but the session is gone, so wipe the order and session. |
| 908 |
if (in_array($session_status, ['ERROR', 'EXPIRED', 'FAILED'])) { |
| 909 |
$this->log(sprintf(__("%1\$s session %2\$d is gone", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id()), 'debug'); |
| 910 |
$this->abandonVippsCheckoutOrder($order); |
| 911 |
} |
| 912 |
|
| 913 |
// This will return either a valid vipps session, nothing, or redirect. |
| 914 |
// From now on it could also return an order without a session. E.g. if the session was cancelled, we call abandonVippsCheckoutOrder and dont redirect anymore. LP 2026-03-17 |
| 915 |
return(array('order'=>$order ? $order->get_id() : false, 'session'=>$session, 'redirect'=>$redirect)); |
| 916 |
} |
| 917 |
|
| 918 |
// Returns HTML of any widgets for the Checkout page IOK 2025-05-13 |
| 919 |
function vipps_ajax_get_widgets () { |
| 920 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 921 |
$order = $current_pending ? wc_get_order($current_pending) : null; |
| 922 |
if (!$order) return ""; |
| 923 |
|
| 924 |
Vipps::set_locale_if_in_header(); |
| 925 |
|
| 926 |
print $this->get_checkout_widgets($order); |
| 927 |
exit(); |
| 928 |
} |
| 929 |
|
| 930 |
// This will, when visiting the cart or another checkout page and Vipps Mobilepay Checkout is active, |
| 931 |
// remove any coupons that can't be both in the cart and in our current Checkout order (thus invalidating the order at the same time) |
| 932 |
// but without the standard, now wrong error message produced in the cart for this. IOK 2025-05-15 |
| 933 |
public function prettily_cleanup_coupons_in_cart($silent=false) { |
| 934 |
$cart = WC()->cart; |
| 935 |
foreach ( $cart->get_applied_coupons() as $code ) { |
| 936 |
$coupon = new WC_Coupon( $code ); |
| 937 |
if ( ! $coupon->is_valid() ) { |
| 938 |
if (!$silent) { |
| 939 |
$msg = sprintf(__("Your coupon code %s has been removed from your cart and your Checkout session has ended. You can add the code again either here or on the Checkout page", 'woo-vipps'), $code); |
| 940 |
// Will only run in the legacy non-gutenberg cart |
| 941 |
wc_add_notice($msg, 'notice'); |
| 942 |
} |
| 943 |
$cart->remove_coupon( $code ); |
| 944 |
} |
| 945 |
} |
| 946 |
} |
| 947 |
|
| 948 |
// This runs very early, in template-redirect; so we can add notices to the cart page. IOK 2025-05-15 |
| 949 |
// If coupons are added in Checkout after the order has been created, we need to change the error message |
| 950 |
// in the Cart when the coupon is noticed as invalid there and removed. IOK 2025-05-15 |
| 951 |
public function handle_coupon_invalidation_in_checkout() { |
| 952 |
global $post; |
| 953 |
if ($post && is_page()) { |
| 954 |
$gw = WC_Gateway_Vipps::instance(); |
| 955 |
$active = (wc_coupons_enabled() && $gw->get_option('vipps_checkout_enabled') == 'yes' && $gw->get_option('checkout_widget_coupon') === 'yes'); |
| 956 |
if ($active) { |
| 957 |
if (has_block("woocommerce/cart")) { |
| 958 |
$this->prettily_cleanup_coupons_in_cart(); |
| 959 |
} else { |
| 960 |
// This is for the old shortcode-based cart; doing the remove several times is safe. IOK 2025-05-15 |
| 961 |
// Then add a new one that adds a different message, also reporting that the vipps session is gone |
| 962 |
// Remove the standard validation code which reports an error |
| 963 |
remove_action('woocommerce_check_cart_items', array(WC()->cart, 'check_cart_coupons'), 1); |
| 964 |
add_action('woocommerce_check_cart_items', array($this, 'prettily_cleanup_coupons_in_cart'), 1); |
| 965 |
} |
| 966 |
} |
| 967 |
} |
| 968 |
} |
| 969 |
|
| 970 |
// Define handlers for some default widgets (if active etc). IOK 2025-05-13 |
| 971 |
public function add_widget_callback_actions () { |
| 972 |
add_filter('woo_vipps_checkout_callback_actions', function ($filters) { |
| 973 |
$filters['submitnotes'] = function ($action, $order) { |
| 974 |
$notes = isset($_REQUEST['callbackdata']['notes']) ? trim($_REQUEST['callbackdata']['notes']) : ''; |
| 975 |
|
| 976 |
// First delete latest customer order note if exists. LP 2025-05-14 |
| 977 |
$order_notes = $order->get_customer_order_notes(); |
| 978 |
$deleted = 0; |
| 979 |
if ($order_notes) { |
| 980 |
$latest_note = $order_notes[0]; |
| 981 |
if (is_a($latest_note, 'WP_Comment')) { |
| 982 |
$deleted = wc_delete_order_note($latest_note->comment_ID); |
| 983 |
} |
| 984 |
} |
| 985 |
$order->set_customer_note(sanitize_text_field($notes)); |
| 986 |
$order->save(); |
| 987 |
|
| 988 |
// Disable the email that gets sent on new order notes. IOK 2025-05-14 |
| 989 |
add_filter('woocommerce_mail_callback', function ($mailer, $mailclass) { |
| 990 |
return '__return_true'; |
| 991 |
}, 999, 2); |
| 992 |
|
| 993 |
|
| 994 |
// Add new note. LP 2025-05-14 |
| 995 |
if ($notes) { |
| 996 |
$order->add_order_note($notes, 1, true); |
| 997 |
return 1; |
| 998 |
} |
| 999 |
return 0; |
| 1000 |
}; |
| 1001 |
|
| 1002 |
$filters['submitcoupon'] = function ($action, $order) { |
| 1003 |
$code = isset($_REQUEST['callbackdata']['code']) ? trim($_REQUEST['callbackdata']['code']) : ''; |
| 1004 |
|
| 1005 |
if ($code) { |
| 1006 |
add_filter('woocommerce_add_success', function ($message) { return ""; }); |
| 1007 |
add_filter('woocommerce_add_error', function ($message) { return ""; }); |
| 1008 |
add_filter('woocommerce_add_notice', function ($message) { return ""; }); |
| 1009 |
|
| 1010 |
do_action('woo_vipps_checkout_before_applying_coupon', $order, $code); |
| 1011 |
if (WC()->cart) { |
| 1012 |
|
| 1013 |
|
| 1014 |
$ok = WC()->cart->apply_coupon($code); |
| 1015 |
if (!$ok || is_wp_error($ok)) { |
| 1016 |
// IOK FIXME GET ACTUAL ERROR HERE |
| 1017 |
throw (new Exception("Failed to apply coupon code $code")); |
| 1018 |
} |
| 1019 |
|
| 1020 |
$coupon = new WC_Coupon($code); |
| 1021 |
$has_free = $coupon->get_free_shipping(); |
| 1022 |
add_filter('woo_vipps_checkout_recalculate_shipping', function ($should_recalc, $had_free, $order, $old_price) use ($has_free) { |
| 1023 |
if ($has_free && !$had_free) { |
| 1024 |
return true; |
| 1025 |
} |
| 1026 |
return $should_recalc; |
| 1027 |
}, 10, 4); |
| 1028 |
|
| 1029 |
} |
| 1030 |
|
| 1031 |
|
| 1032 |
$res = $order->apply_coupon($code); |
| 1033 |
if (is_wp_error($res)) throw (new Exception("Failed to apply coupon code $code")); |
| 1034 |
do_action('woo_vipps_checkout_after_applying_coupon', $order, $code); |
| 1035 |
|
| 1036 |
return 1; |
| 1037 |
} |
| 1038 |
return 0; |
| 1039 |
}; |
| 1040 |
|
| 1041 |
$filters['removecoupon'] = function ($action, $order) { |
| 1042 |
$code = isset($_REQUEST['callbackdata']['code']) ? trim($_REQUEST['callbackdata']['code']) : ''; |
| 1043 |
if ($code) { |
| 1044 |
do_action('woo_vipps_checkout_before_removing_coupon', $order, $code); |
| 1045 |
// Ensure the cart too loses the coupon |
| 1046 |
if (WC()->cart) { |
| 1047 |
$ok = WC()->cart->remove_coupon($code); |
| 1048 |
// can't do much if this fails so |
| 1049 |
} |
| 1050 |
$res = $order->remove_coupon($code); |
| 1051 |
do_action('woo_vipps_checkout_after_removing_coupon', $order, $code); |
| 1052 |
|
| 1053 |
$coupon = new WC_Coupon($code); |
| 1054 |
$has_free = $coupon->get_free_shipping(); |
| 1055 |
add_filter('woo_vipps_checkout_recalculate_shipping', function ($should_recalc, $had_free, $order, $old_price) use ($has_free) { |
| 1056 |
if ($has_free && $had_free) { |
| 1057 |
return true; |
| 1058 |
} |
| 1059 |
return $should_recalc; |
| 1060 |
}, 10, 4); |
| 1061 |
|
| 1062 |
|
| 1063 |
if ($res) return 1; |
| 1064 |
} |
| 1065 |
return 1; // just do it ? if errors happen here, the coupon *gets stuck*? FIXME IOK 2025-05-15 |
| 1066 |
}; |
| 1067 |
return $filters; |
| 1068 |
}); |
| 1069 |
} |
| 1070 |
|
| 1071 |
|
| 1072 |
// Add premade widgets depending on users settings. LP 2025-05-14 |
| 1073 |
// For now, coupon code widget and order notes widget. |
| 1074 |
function maybe_add_widgets() { |
| 1075 |
// Premade widget: coupon code. LP 2025-05-08 |
| 1076 |
$widgets = []; |
| 1077 |
$use_widget_coupon = wc_coupons_enabled() && $this->gateway()->get_option('checkout_widget_coupon') === 'yes'; |
| 1078 |
|
| 1079 |
// Premade widget: order note. LP 2025-05-12 |
| 1080 |
$use_widget_ordernotes = $this->gateway()->get_option('checkout_widget_ordernotes') === 'yes'; |
| 1081 |
if ($use_widget_coupon || $use_widget_ordernotes) { |
| 1082 |
add_filter('woo_vipps_checkout_widgets', function ($widgets) use ($use_widget_coupon, $use_widget_ordernotes) { |
| 1083 |
if ($use_widget_coupon) { |
| 1084 |
$widgets[] = [ |
| 1085 |
'title' => __('Coupon code', 'woo-vipps'), |
| 1086 |
'id' => 'vipps_checkout_widget_coupon', |
| 1087 |
'class' => 'vipps_checkout_widget_premade', |
| 1088 |
'callback' => function($order) {?> |
| 1089 |
<div id="vipps_checkout_widget_coupon_active_codes_container" style="display:none;"> |
| 1090 |
Active codes |
| 1091 |
<div id="vipps_checkout_widget_coupon_active_codes_container_codes"> |
| 1092 |
<?php |
| 1093 |
if ($order): |
| 1094 |
foreach ($order->get_coupon_codes() as $code):?> |
| 1095 |
<div class="vipps_checkout_widget_coupon_active_code_box" id="vipps_checkout_widget_coupon_active_code_<?php echo $code;?>"> |
| 1096 |
<span class="vipps_checkout_widget_coupon_active_code"><?php echo $code;?></span> |
| 1097 |
<span class="vipps_checkout_widget_coupon_delete">✕</span> |
| 1098 |
</div> |
| 1099 |
<?php endforeach; endif;?> |
| 1100 |
</div> |
| 1101 |
</div> |
| 1102 |
<form id="vipps_checkout_widget_coupon_form"> |
| 1103 |
<label for="vipps_checkout_widget_coupon_code" class="vipps_checkout_widget_small"><?php echo __('Enter your code', 'woo-vipps')?></label> |
| 1104 |
<span id="vipps_checkout_widget_coupon_error" class="vipps_checkout_widget_error" style="display:none;"><?php echo __('Invalid coupon code', 'woo-vipps') ?></span> |
| 1105 |
<span id="vipps_checkout_widget_coupon_delete_error" class="vipps_checkout_widget_error" style="display:none;"><?php echo __('Could not remove coupon', 'woo-vipps') ?></span> |
| 1106 |
<span id="vipps_checkout_widget_coupon_success" class="vipps_checkout_widget_success" style="display:none;"><?php echo __('Coupon code added!', 'woo-vipps') ?></span> |
| 1107 |
<input required id="vipps_checkout_widget_coupon_code" class="vipps_checkout_widget_input" type="text" name="code"/> |
| 1108 |
<button type="submit" class="vippspurple2 vipps_checkout_widget_button"><?php echo __('Add', 'woo-vipps')?></button> |
| 1109 |
</form> |
| 1110 |
<?php |
| 1111 |
} |
| 1112 |
]; |
| 1113 |
} |
| 1114 |
if ($use_widget_ordernotes) { |
| 1115 |
$widgets[] = [ |
| 1116 |
'title' => __('Order notes', 'woo-vipps'), |
| 1117 |
'id' => 'vipps_checkout_widget_ordernotes', |
| 1118 |
'class' => 'vipps_checkout_widget_premade', |
| 1119 |
'callback' => function($order) { ?> |
| 1120 |
<form id="vipps_checkout_widget_ordernotes_form"> |
| 1121 |
<div for="vipps_checkout_widget_ordernotes_input" class="vipps_checkout_widget_info"><?php echo __('Is there anything you wish to inform the store about? Include it here', 'woo-vipps')?></div> |
| 1122 |
<label for="vipps_checkout_widget_ordernotes_input" class="vipps_checkout_widget_small"><?php echo __('Notes', 'woo-vipps')?></label> |
| 1123 |
<span id="vipps_checkout_widget_ordernotes_error" class="vipps_checkout_widget_error" style="display:none;"><?php echo __('Something went wrong', 'woo-vipps') ?></span> |
| 1124 |
<span id="vipps_checkout_widget_ordernotes_success" class="vipps_checkout_widget_success" style="display:none;"><?php echo __('Saved', 'woo-vipps') ?></span> |
| 1125 |
<input id="vipps_checkout_widget_ordernotes_input" class="vipps_checkout_widget_input" type="text" name="notes" value="<?php if ($order) { |
| 1126 |
$order_notes = $order->get_customer_order_notes(); |
| 1127 |
if ($order_notes) { |
| 1128 |
$latest_note = $order_notes[0]; |
| 1129 |
if (is_a($latest_note, 'WP_Comment')) { |
| 1130 |
echo $latest_note->comment_content; |
| 1131 |
} |
| 1132 |
} |
| 1133 |
} ?>"/> |
| 1134 |
<button type="submit" class="vippspurple2 vipps_checkout_widget_button"><?php echo __('Save', 'woo-vipps')?></button> |
| 1135 |
</form> |
| 1136 |
<?php |
| 1137 |
} |
| 1138 |
]; |
| 1139 |
} |
| 1140 |
return $widgets; |
| 1141 |
}); |
| 1142 |
} |
| 1143 |
|
| 1144 |
return $widgets; |
| 1145 |
} |
| 1146 |
|
| 1147 |
// This will display widgets like coupon codes, order notes etc on the Vipps Checkout page IOK 2025-05-02 |
| 1148 |
function get_checkout_widgets($order) { |
| 1149 |
// Array of tables of [title, id, callback, class]. |
| 1150 |
// $default_widgets = $this->get_checkout_default_widgets($order); |
| 1151 |
$this->maybe_add_widgets(); |
| 1152 |
|
| 1153 |
// NB: We may not have an order at this point. IOK 2025-05-02 |
| 1154 |
$widgets = apply_filters('woo_vipps_checkout_widgets', [], $order); |
| 1155 |
|
| 1156 |
if (empty($widgets)) return ""; |
| 1157 |
ob_start(); |
| 1158 |
echo "<div class='vipps_checkout_widget_wrapper' style='display:none;'>"; |
| 1159 |
foreach ($widgets as $widget) { |
| 1160 |
$id = $widget['id'] ?? ""; |
| 1161 |
$title = $widget['title'] ?? ""; |
| 1162 |
$class = $widget['class'] ?? ""; |
| 1163 |
$callback = $widget['callback'] ?? ""; |
| 1164 |
|
| 1165 |
if (!$title || !$callback) continue; |
| 1166 |
|
| 1167 |
$idattr = $id ? "id='" . esc_attr($id) . "'" : ""; |
| 1168 |
$classattr = "class='vipps_checkout_widget" . ($class ? " " . esc_attr($class) : "") . "'"; |
| 1169 |
echo "<div $idattr $classattr>"; |
| 1170 |
echo "<div class='vipps_checkout_widget_title accordion'>" . esc_html($title) . "<span class='vipps_checkout_widget_icon'></span></div>"; |
| 1171 |
echo "<div class='vipps_checkout_body'>"; |
| 1172 |
call_user_func($callback, $order); |
| 1173 |
echo "</div>"; |
| 1174 |
echo "</div>"; |
| 1175 |
} |
| 1176 |
echo "</div>"; |
| 1177 |
$res = ob_get_clean(); |
| 1178 |
|
| 1179 |
return $res; |
| 1180 |
} |
| 1181 |
|
| 1182 |
function vipps_checkout_shortcode ($atts, $content) { |
| 1183 |
// No point in expanding this unless we are actually doing the checkout. IOK 2021-09-03 |
| 1184 |
if (is_admin()) return; |
| 1185 |
if (wp_doing_ajax()) return; |
| 1186 |
if (defined('REST_REQUEST') && REST_REQUEST ) return; |
| 1187 |
wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true ); |
| 1188 |
add_filter('woo_vipps_is_vipps_checkout', '__return_true'); |
| 1189 |
|
| 1190 |
// Defer to the normal code for endpoints IOK 2022-12-09 |
| 1191 |
if (is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' )) { |
| 1192 |
return do_shortcode("[woocommerce_checkout]"); |
| 1193 |
} |
| 1194 |
|
| 1195 |
if (!WC()->cart || WC()->cart->is_empty() ) { |
| 1196 |
$this->abandonVippsCheckoutOrder(false); |
| 1197 |
ob_start(); |
| 1198 |
wc_get_template( 'cart/cart-empty.php' ); |
| 1199 |
return ob_get_clean(); |
| 1200 |
} |
| 1201 |
|
| 1202 |
WC()->session->set( 'chosen_payment_method', 'vipps'); // This is to stop KCO from trying to replace Vipps Checkout with KCO and failing. IOK 2024-05-13 |
| 1203 |
|
| 1204 |
// Previously registered, now enqueue this script which should then appear in the footer. |
| 1205 |
// Then call a hook for people adding custom javascript. This needs to be moved to template redirect. IOK 2025-06-02 |
| 1206 |
wp_enqueue_script('vipps-checkout'); |
| 1207 |
do_action('woo_vipps_checkout_enqueue_scripts'); |
| 1208 |
|
| 1209 |
do_action('vipps_checkout_before_get_session'); |
| 1210 |
|
| 1211 |
// We need to be able to check if we still have a live, good session, in which case |
| 1212 |
// we can open the iframe directly. Otherwise, the form we are going to output will |
| 1213 |
// create the iframe after a button press which will create a new order. |
| 1214 |
$sessioninfo = $this->vipps_checkout_current_pending_session(); |
| 1215 |
|
| 1216 |
$out = ""; // Start generating output already to make debugging easier |
| 1217 |
|
| 1218 |
// This is the current pending order id, if it exists. Will be used to restart orders etc . IOK 2023-08-15 FIXME |
| 1219 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 1220 |
|
| 1221 |
if ($sessioninfo['redirect']) { |
| 1222 |
// This is always either the thankyou page or home_url() IOK 2021-09-03 |
| 1223 |
$redir = json_encode($sessioninfo['redirect']); |
| 1224 |
$out .= "<script>window.location.replace($redir);</script>"; |
| 1225 |
return $out; |
| 1226 |
} |
| 1227 |
|
| 1228 |
// Now the normal case. |
| 1229 |
$errortext = apply_filters('woo_vipps_checkout_error', __('An error has occured - please reload the page to restart your transaction, or return to the shop', 'woo-vipps')); |
| 1230 |
$expiretext = apply_filters('woo_vipps_checkout_error', __('Your session has expired - please reload the page to restart, or return to the shop', 'woo-vipps')); |
| 1231 |
|
| 1232 |
$out .= Vipps::instance()->spinner(); |
| 1233 |
|
| 1234 |
if (!$sessioninfo['session']) { |
| 1235 |
$out .= "<div style='visibility:hidden' class='vipps_checkout_startdiv'>"; |
| 1236 |
$out .= "<h2>" . sprintf(__('Press the button to complete your order with %1$s!', 'woo-vipps'), Vipps::instance()->get_payment_method_name()) . "</h2>"; |
| 1237 |
$out .= '<div class="vipps_checkout_button_wrapper" ><button type="submit" class="button vipps_checkout_button vippsorange" value="1">' . sprintf(__('%1$s', 'woo-vipps'), Vipps::CheckoutName()) . '</button></div>'; |
| 1238 |
$out .= "</div>"; |
| 1239 |
} |
| 1240 |
|
| 1241 |
// If we have an actual live session right now, add it to the page on load. Otherwise, the session will be started using ajax after the page loads (and is visible) |
| 1242 |
if ($sessioninfo['session']) { |
| 1243 |
$token = $sessioninfo['session']['token']; // From Vipps |
| 1244 |
$src = $sessioninfo['session']['checkoutFrontendUrl']; // From Vipps |
| 1245 |
$out .= "<script>VippsSessionState = " . json_encode(array('token'=>$token, 'checkoutFrontendUrl'=>$src)) . ";</script>\n"; |
| 1246 |
} else { |
| 1247 |
$out .= "<script>VippsSessionState = null;</script>\n"; |
| 1248 |
} |
| 1249 |
|
| 1250 |
// Mount point for widgets. IOK 2025-05-13 |
| 1251 |
// starts hidden. is shown when vipps checkout loads successfully. LP 2025-05-12 |
| 1252 |
$out .= "<div id='vippscheckoutframe'>"; |
| 1253 |
$out .= "<div id='vipps_checkout_widget_mount'></div>"; |
| 1254 |
|
| 1255 |
$out .= "</div>"; |
| 1256 |
$out .= "<div style='display:none' id='vippscheckouterror'><p>$errortext</p></div>"; |
| 1257 |
$out .= "<div style='display:none' id='vippscheckoutexpired'><p>$expiretext</p></div>"; |
| 1258 |
|
| 1259 |
|
| 1260 |
// We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24 |
| 1261 |
$classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout"); |
| 1262 |
$out .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>"; |
| 1263 |
$out .= "<input type='hidden' id='vippsorderid' name='_vippsorder' value='" . intval($current_pending) . "' />"; |
| 1264 |
// And this is for the order attribution feature of Woo 8.5 IOK 2024-01-09 |
| 1265 |
if (WC_Gateway_Vipps::instance()->get_option('vippsorderattribution') == 'yes') { |
| 1266 |
$out .= '<input type="hidden" id="vippsorderattribution" value="1" />'; |
| 1267 |
ob_start(); |
| 1268 |
do_action( 'woocommerce_after_order_notes'); |
| 1269 |
$out .= ob_get_clean(); |
| 1270 |
} |
| 1271 |
$out .= wp_nonce_field('do_vipps_checkout','vipps_checkout_sec',1,false); |
| 1272 |
$out .= "</form>"; |
| 1273 |
|
| 1274 |
return $out; |
| 1275 |
} |
| 1276 |
|
| 1277 |
|
| 1278 |
public function cart_changed() { |
| 1279 |
// Don't do this if we are changing the cart in a Vipps Checkout callback. IOK 2025-05-15 |
| 1280 |
if (apply_filters('woo_vipps_is_checkout_callback', false)) { |
| 1281 |
return; |
| 1282 |
} |
| 1283 |
$current_pending = is_a(WC()->session, 'WC_Session') ? WC()->session->get('vipps_checkout_current_pending') : false; |
| 1284 |
$order = $current_pending ? wc_get_order($current_pending) : null; |
| 1285 |
if (!$order) return; |
| 1286 |
$this->log(sprintf(__("%1\$s: cart changed while session %2\$d in progress - now cancelled", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id()), 'debug'); |
| 1287 |
$this->abandonVippsCheckoutOrder($order); |
| 1288 |
} |
| 1289 |
|
| 1290 |
public function abandonVippsCheckoutOrder($order) { |
| 1291 |
|
| 1292 |
if (WC()->session) { |
| 1293 |
WC()->session->set('vipps_checkout_current_pending',0); |
| 1294 |
WC()->session->set('vipps_address_hash', false); |
| 1295 |
} |
| 1296 |
|
| 1297 |
if (is_a($order, 'WC_Order') && $order->get_status() == 'pending') { |
| 1298 |
// We want to kill orders that have failed, or that the user has abandoned. To do this, |
| 1299 |
// we must ensure that no race or other mechanism kills the order while or after being paid. |
| 1300 |
// if order is in the process of being finalized, don't kill it |
| 1301 |
if (Vipps::instance()->isLocked($order)) { |
| 1302 |
return false; |
| 1303 |
} |
| 1304 |
// Get it again to ensure we have all the info, and check status again |
| 1305 |
clean_post_cache($order->get_id()); |
| 1306 |
$order = wc_get_order($order->get_id()); |
| 1307 |
if ($order->get_status() != 'pending') return false; |
| 1308 |
|
| 1309 |
// And to be extra sure, check status at vipps |
| 1310 |
$session = $order->get_meta('_vipps_checkout_session'); |
| 1311 |
if (!$session) return false; |
| 1312 |
|
| 1313 |
try { |
| 1314 |
$polldata = $this->gateway()->api->checkout_get_session_info($order); |
| 1315 |
$sessionState = (!empty($polldata) && is_array($polldata) && isset($polldata['sessionState'])) ? $polldata['sessionState'] : ""; |
| 1316 |
$this->log("Checking Checkout status on cart/order change for " . $order->get_id() . " $sessionState ", 'debug'); |
| 1317 |
if ($sessionState == 'PaymentSuccessful' || $sessionState == 'PaymentInitiated') { |
| 1318 |
// If we have started payment, we do not kill the order. |
| 1319 |
$this->log("Checkout payment started - cannot cancel for " . $order->get_id(), 'debug'); |
| 1320 |
return false; |
| 1321 |
} |
| 1322 |
} catch (Exception $e) { |
| 1323 |
$this->log(sprintf(__('Could not get Checkout status for order %1$s in progress while cancelling', 'woo-vipps'), $order->get_id()), 'debug'); |
| 1324 |
} |
| 1325 |
|
| 1326 |
|
| 1327 |
// NB: This can *potentially* be revived by a callback! |
| 1328 |
$this->log(sprintf(__('Cancelling Checkout order because order changed: %1$s', 'woo-vipps'), $order->get_id()), 'debug'); |
| 1329 |
$order->set_status('cancelled', __("Order specification changed - this order abandoned by customer in Checkout ", 'woo-vipps'), false); |
| 1330 |
// Also mark for deletion. |
| 1331 |
$order->update_meta_data('_vipps_delendum',1); |
| 1332 |
$order->save(); |
| 1333 |
} |
| 1334 |
} |
| 1335 |
|
| 1336 |
public function get_vipps_checkout_status($order) { |
| 1337 |
$status = $this->gateway()->api->checkout_get_session_info($order); |
| 1338 |
return $status; |
| 1339 |
} |
| 1340 |
|
| 1341 |
|
| 1342 |
public function maybe_override_checkout_page_id ($id) { |
| 1343 |
// Only do this if Vipps Checkout was ever activated |
| 1344 |
$vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 1345 |
if (!$vipps_checkout_activated) return $id; |
| 1346 |
|
| 1347 |
// The gutenberg block (and other pages) calls the checkout-page-id function *a lot* so let's just check once |
| 1348 |
if ($this->payid) return $this->payid; |
| 1349 |
|
| 1350 |
// If we are on a checkout page, don't go other places please |
| 1351 |
if (is_page()){ |
| 1352 |
global $post; |
| 1353 |
// The unfiltered checkout page from woo |
| 1354 |
if ($post && $post->ID == get_option( 'woocommerce_checkout_page_id' )) { |
| 1355 |
$this->payid = $id; |
| 1356 |
return $id; |
| 1357 |
} |
| 1358 |
// any other page with a gutenberg checkout block. We don't need to test for the shortcode, that works fine. |
| 1359 |
if ($post && has_block( 'woocommerce/checkout', $post->post_content) ) { |
| 1360 |
$this->payid = $id; |
| 1361 |
return $id; |
| 1362 |
} |
| 1363 |
// If this is "pay for order", also don't do anything. |
| 1364 |
$orderid = absint(get_query_var( 'order-pay')); |
| 1365 |
if ($orderid) { |
| 1366 |
$this->payid = $id; |
| 1367 |
return $id; |
| 1368 |
} |
| 1369 |
} |
| 1370 |
|
| 1371 |
// Else, if Vipps Checkout is enabled, can be used etc, use that. |
| 1372 |
$checkoutid = $this->gateway()->vipps_checkout_available(); |
| 1373 |
if ($checkoutid) { |
| 1374 |
$this->payid = $checkoutid; |
| 1375 |
return $checkoutid; |
| 1376 |
} |
| 1377 |
|
| 1378 |
return $id; |
| 1379 |
} |
| 1380 |
|
| 1381 |
public function woocommerce_loaded () { |
| 1382 |
# This implements the Vipps Checkout replacement checkout page for those that wants to use that, by filtering the checkout page id. |
| 1383 |
add_filter('woocommerce_get_checkout_page_id', array($this, 'maybe_override_checkout_page_id'), 10, 1); |
| 1384 |
|
| 1385 |
// This is for the 'other payment method' thing in Vipps Checkout - we store address info |
| 1386 |
// in session. IOK 2024-05-13 |
| 1387 |
add_filter('woocommerce_checkout_fields', function ($fields) { |
| 1388 |
if (empty(WC()->session)) return $fields; |
| 1389 |
$possibly_address = WC()->session->get('vc_address'); |
| 1390 |
|
| 1391 |
if (!$possibly_address) return $fields; |
| 1392 |
WC()->session->set('vc_address', null); |
| 1393 |
|
| 1394 |
foreach($fields['billing'] as $key => &$bdata) { |
| 1395 |
$v = trim($possibly_address[$key] ?? ""); |
| 1396 |
if ($v) { |
| 1397 |
$bdata['default'] = $v; |
| 1398 |
} |
| 1399 |
} |
| 1400 |
foreach($fields['shipping'] as $key => &$sdata) { |
| 1401 |
$v = trim($possibly_address[$key] ?? ""); |
| 1402 |
if ($v) { |
| 1403 |
$sdata['default'] = $v; |
| 1404 |
} |
| 1405 |
} |
| 1406 |
return $fields; |
| 1407 |
}); |
| 1408 |
|
| 1409 |
} |
| 1410 |
|
| 1411 |
|
| 1412 |
public function woocommerce_settings_pages ($settings) { |
| 1413 |
$vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 1414 |
if (!$vipps_checkout_activated) return $settings; |
| 1415 |
$i = -1; |
| 1416 |
foreach($settings as $entry) { |
| 1417 |
$i++; |
| 1418 |
if ($entry['type'] == 'sectionend' && $entry['id'] == 'advanced_page_options') { |
| 1419 |
break; |
| 1420 |
} |
| 1421 |
} |
| 1422 |
if ($i > 0) { |
| 1423 |
|
| 1424 |
$vippspagesettings = array( |
| 1425 |
array( |
| 1426 |
'title' => sprintf(__( '%1$s Page', 'woo-vipps' ), Vipps::CheckoutName()), |
| 1427 |
'desc' => sprintf(__('This page is used for the alternative %1$s page, which you can choose to use instead of the normal WooCommerce checkout page. ', 'woo-vipps'), Vipps::CheckoutName()) . sprintf( __( 'Page contents: [%1$s]', 'woocommerce' ), 'vipps_checkout') , |
| 1428 |
'id' => 'woocommerce_vipps_checkout_page_id', |
| 1429 |
'type' => 'single_select_page_with_search', |
| 1430 |
'default' => '', |
| 1431 |
'class' => 'wc-page-search', |
| 1432 |
'css' => 'min-width:300px;', |
| 1433 |
'args' => array( |
| 1434 |
'exclude' => |
| 1435 |
array( |
| 1436 |
wc_get_page_id( 'myaccount' ), |
| 1437 |
), |
| 1438 |
), |
| 1439 |
'desc_tip' => true, |
| 1440 |
'autoload' => false, |
| 1441 |
)); |
| 1442 |
array_splice($settings, $i, 0, $vippspagesettings); |
| 1443 |
} |
| 1444 |
|
| 1445 |
return $settings; |
| 1446 |
} |
| 1447 |
|
| 1448 |
|
| 1449 |
// Translate from the Express Checkout shipping method format to the Vipps Checkout shipping |
| 1450 |
// format, which is slightly different. The ratemap maps from a method key to its WC_Shipping_Rate, and the method map does |
| 1451 |
// the same for WP_Shipping_Method. |
| 1452 |
// The ratemap will in the end be stored in the order and used to retrieve the selected shipping method. IOK 2025-08-15 |
| 1453 |
// IOK 2025-05-07 Also treat PickupLocation specially. We'll return at most one of these, and if there are more than one, we will add the locations available as |
| 1454 |
// metadata. |
| 1455 |
public function format_shipping_methods ($return, &$ratemap, $methodmap, $order) { |
| 1456 |
$translated = array(); |
| 1457 |
$currency = get_woocommerce_currency(); |
| 1458 |
$pickupLocation = null; // if we have a pickup_location rate, set this to be the first one. IOK 2025-05-07 |
| 1459 |
|
| 1460 |
foreach ($return['shippingDetails'] as $m) { |
| 1461 |
$m2 = array(); |
| 1462 |
|
| 1463 |
$m2['isDefault'] = (bool) (($m['isDefault']=='Y') ? true : false); // type bool here, but not in the other api |
| 1464 |
$m2['priority'] = $m['priority']; |
| 1465 |
$m2['amount'] = array( |
| 1466 |
'value' => round(100*$m['shippingCost']), // Unlike eComm, this uses cents |
| 1467 |
'currency' => $currency // May want to use the orders' currency instead here, since it exists. |
| 1468 |
); |
| 1469 |
$m2['brand'] = "OTHER"; |
| 1470 |
$m2['title'] = $m['shippingMethod']; |
| 1471 |
$m2['id'] = $m['shippingMethodId']; |
| 1472 |
|
| 1473 |
$rate = $ratemap[$m2['id']]; |
| 1474 |
$shipping_method = $methodmap[$m2['id']]; |
| 1475 |
|
| 1476 |
// If we have pickup_location-s, only use the first one. IOK 2025-05-07 |
| 1477 |
if ($rate->method_id == 'pickup_location') { |
| 1478 |
if (!$pickupLocation) { |
| 1479 |
$pickupLocation = &$m2; |
| 1480 |
} else { |
| 1481 |
continue; |
| 1482 |
} |
| 1483 |
} |
| 1484 |
|
| 1485 |
|
| 1486 |
// Some data must be visible in the Order screen, so add meta data, also, for dynamic pricing check that free shipping hasn't been reached |
| 1487 |
$meta = $rate->get_meta_data(); |
| 1488 |
|
| 1489 |
// The description is normally only stored only in the shipping method |
| 1490 |
if ($shipping_method) { |
| 1491 |
// Support dynamic cost alongside free shipping using the new api where NULL is dynamic pricing 2023-07-17 |
| 1492 |
if (isset($shipping_method->instance_settings['dynamic_cost']) && $shipping_method->instance_settings['dynamic_cost'] == 'yes') { |
| 1493 |
if (!isset($meta['free_shipping']) || !$meta['free_shipping']) { |
| 1494 |
$m2['amount'] = null; |
| 1495 |
} |
| 1496 |
} |
| 1497 |
$m2['description'] = $shipping_method->get_option('description', ''); |
| 1498 |
} else { |
| 1499 |
$m2['description'] = ""; |
| 1500 |
} |
| 1501 |
|
| 1502 |
|
| 1503 |
// Allow shipping methods to add pickup points data IOK 2025-04-08 |
| 1504 |
$delivery = []; |
| 1505 |
$pickup_points = apply_filters('woo_vipps_shipping_method_pickup_points', [], $rate, $shipping_method, $order); |
| 1506 |
if ($pickup_points) { |
| 1507 |
$filtered = []; |
| 1508 |
foreach($pickup_points as $point) { |
| 1509 |
$ok = true; |
| 1510 |
$entry = []; |
| 1511 |
foreach(['address', 'city', 'country', 'id', 'name', 'postalCode'] as $key) { |
| 1512 |
if (!isset($point[$key])) { |
| 1513 |
$this->log(__('Cannot add pickup point: A pickup point needs to have keys id, name, address, city, postalCode and country: ', 'woo-vipps') . print_r($point, true), 'error'); |
| 1514 |
$ok = false; |
| 1515 |
break; |
| 1516 |
} else { |
| 1517 |
$entry[$key] = $point[$key]; |
| 1518 |
} |
| 1519 |
} |
| 1520 |
foreach(['openingHours', 'leadTime'] as $key) { |
| 1521 |
if (isset($point[$key])) { |
| 1522 |
$entry[$key] = $point[$key]; |
| 1523 |
} |
| 1524 |
} |
| 1525 |
|
| 1526 |
if ($ok && !empty($entry)) { |
| 1527 |
$filtered[] = $entry; |
| 1528 |
} |
| 1529 |
} |
| 1530 |
$delivery['pickupPoints'] = $filtered; |
| 1531 |
$m2['type'] = 'PICKUP_POINT'; |
| 1532 |
|
| 1533 |
// Remove name of location for PickupLocation if we do have choices. IOK 2025-05-07 |
| 1534 |
if ($m2 == $pickupLocation && count($filtered) > 1) { |
| 1535 |
$m2['title'] = $shipping_method->title; |
| 1536 |
} |
| 1537 |
|
| 1538 |
|
| 1539 |
} |
| 1540 |
|
| 1541 |
// Timeslots. This is for home delivery options, should have values id (string), date (date), start (time), end (time). |
| 1542 |
// IOK 2025-04-10 |
| 1543 |
$timeslots = apply_filters('woo_vipps_shipping_method_timeslots', [], $rate, $shipping_method, $order); |
| 1544 |
if (!empty($timeslots)) { |
| 1545 |
$filtered = []; |
| 1546 |
foreach($timeslots as $timeslot) { |
| 1547 |
$entry = []; |
| 1548 |
$ok = true; |
| 1549 |
foreach(['id', 'date', 'start', 'end'] as $key) { |
| 1550 |
if (!isset($timeslot[$key])) { |
| 1551 |
$this->log(__('Cannot add timeslot: A timeslot needs to have keys id, date, start and end: ', 'woo-vipps') . print_r($timeslot, true), 'error'); |
| 1552 |
$ok = false; |
| 1553 |
break; |
| 1554 |
} else { |
| 1555 |
$entry[$key] = $timeslot[$key]; |
| 1556 |
} |
| 1557 |
} |
| 1558 |
if ($ok && !empty($entry)) { |
| 1559 |
$filtered[] = $entry; |
| 1560 |
} |
| 1561 |
} |
| 1562 |
$delivery['timeslots']=$filtered; |
| 1563 |
$m2['type'] = 'HOME_DELIVERY'; |
| 1564 |
} |
| 1565 |
|
| 1566 |
// add leadTime data to "Mailbox" types |
| 1567 |
$leadTime = apply_filters('woo_vipps_shipping_method_lead_time', null, $rate, $shipping_method, $order); |
| 1568 |
if (!empty($leadTime)) { |
| 1569 |
$entry = []; |
| 1570 |
$ok = true; |
| 1571 |
foreach(['earliest', 'latest'] as $key) { |
| 1572 |
if (!isset($leadTime[$key])) { |
| 1573 |
$ok = false; break; |
| 1574 |
} |
| 1575 |
$entry[$key] = $leadTime[$key]; |
| 1576 |
} |
| 1577 |
if ($ok && !empty($entry)) { |
| 1578 |
$delivery['leadTime'] = $entry; |
| 1579 |
} |
| 1580 |
} |
| 1581 |
|
| 1582 |
if (!empty($delivery)) { |
| 1583 |
$m2['delivery'] = $delivery; |
| 1584 |
} |
| 1585 |
|
| 1586 |
if (isset($meta['brand'])) { |
| 1587 |
$m2['brand'] = $meta['brand']; |
| 1588 |
} else { |
| 1589 |
// specialcase some known methods so they get brands, and put the label into the description |
| 1590 |
if ($shipping_method && is_a($shipping_method, 'WC_Shipping_Method') && get_class($shipping_method) == 'WC_Shipping_Method_Bring_Pro') { |
| 1591 |
$m2['brand'] = "POSTEN"; |
| 1592 |
$m2['description'] = $rate->get_label(); |
| 1593 |
} |
| 1594 |
$m2['brand'] = apply_filters('woo_vipps_shipping_method_brand', $m2['brand'],$shipping_method, $rate); |
| 1595 |
} |
| 1596 |
|
| 1597 |
if ($m2['brand'] != "OTHER" && isset($meta['type'])) { |
| 1598 |
$m2['type'] = $meta['type']; |
| 1599 |
if ($m2['brand'] === 'POSTI') { |
| 1600 |
$m2['type'] = 'PICKUP_POINT'; // Temp fix. Posti only supports pickup point now. LP 2025-10-17 |
| 1601 |
} |
| 1602 |
} |
| 1603 |
|
| 1604 |
// Old filter kept for backwards compatibility |
| 1605 |
$m2['description'] = apply_filters('woo_vipps_shipping_method_description', $m2['description'], $rate, $shipping_method); |
| 1606 |
$translated[] = $m2; |
| 1607 |
} |
| 1608 |
|
| 1609 |
$return['shippingDetails'] = $translated; |
| 1610 |
unset($return['addressId']); // Not used it seems for checkout |
| 1611 |
unset($return['orderId']); |
| 1612 |
|
| 1613 |
$return = apply_filters('woo_vipps_checkout_json_shipping_methods', $return, $order); |
| 1614 |
return $return; |
| 1615 |
} |
| 1616 |
|
| 1617 |
|
| 1618 |
|
| 1619 |
} |
| 1620 |
|