| 1 |
<?php |
| 2 |
/* |
| 3 |
Delegate class for talking to Vipps MobilePay, encapsulating all the low-level behaviour and mapping error codes to exceptions |
| 4 |
|
| 5 |
This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce |
| 6 |
Copyright (c) 2019 WP-Hosting AS |
| 7 |
|
| 8 |
MIT License |
| 9 |
|
| 10 |
Copyright (c) 2019 WP-Hosting AS |
| 11 |
|
| 12 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
| 13 |
of this software and associated documentation files (the "Software"), to deal |
| 14 |
in the Software without restriction, including without limitation the rights |
| 15 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 16 |
copies of the Software, and to permit persons to whom the Software is |
| 17 |
furnished to do so, subject to the following conditions: |
| 18 |
|
| 19 |
The above copyright notice and this permission notice shall be included in all |
| 20 |
copies or substantial portions of the Software. |
| 21 |
|
| 22 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 23 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 24 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 25 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 26 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 27 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 28 |
SOFTWARE. |
| 29 |
|
| 30 |
*/ |
| 31 |
if ( ! defined('ABSPATH') ) { |
| 32 |
exit; // Exit if accessed directly |
| 33 |
} |
| 34 |
require_once(dirname(__FILE__) . "/VippsApi.class.php"); |
| 35 |
|
| 36 |
class WC_Gateway_Vipps extends WC_Payment_Gateway { |
| 37 |
public $form_fields = null; |
| 38 |
public $dev_form_fields = null; |
| 39 |
public $id = 'vipps'; |
| 40 |
public $icon = ''; |
| 41 |
public $has_fields = true; |
| 42 |
public $method_title = 'Vipps MobilePay'; |
| 43 |
public $title = 'Vipps MobilePay'; |
| 44 |
public $method_description = ""; |
| 45 |
public $apiurl = null; |
| 46 |
public $testapiurl = null; |
| 47 |
public $api = null; |
| 48 |
public $supports = null; |
| 49 |
public $express_checkout_supported_product_types; |
| 50 |
|
| 51 |
public $captured_statuses; |
| 52 |
|
| 53 |
private static $instance = null; // This class uses the singleton pattern to make actions easier to handle |
| 54 |
|
| 55 |
protected $keyset = null; // This will contain all api keys etc for the gateway, keyed on the merchant serial number. |
| 56 |
|
| 57 |
// Just to avoid calculating these alot |
| 58 |
private $page_templates = null; |
| 59 |
private $page_list = null; |
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
// This returns the singleton instance of this class |
| 64 |
public static function instance() { |
| 65 |
if (null === self::$instance) { |
| 66 |
self::$instance = new self(); |
| 67 |
} |
| 68 |
return self::$instance; |
| 69 |
} |
| 70 |
|
| 71 |
public function add_image_upload_setting_widget () { |
| 72 |
add_action('admin_enqueue_scripts', function ($suff) { |
| 73 |
if ($suff == 'woocommerce_page_wc-settings' && (($_REQUEST['section'] ?? false) == 'vipps')) { |
| 74 |
if (!did_action('wp_enqueue_media')) { |
| 75 |
wp_enqueue_media(); |
| 76 |
} |
| 77 |
} |
| 78 |
}); |
| 79 |
} |
| 80 |
|
| 81 |
|
| 82 |
// Generates html for the woo_vipps_image settings widget type |
| 83 |
public function generate_woo_vipps_image_html ($key, $field) { |
| 84 |
$field_key = $this->get_field_key( $key ); |
| 85 |
$defaults = array( |
| 86 |
'title' => '', |
| 87 |
'disabled' => false, |
| 88 |
'class' => '', |
| 89 |
'css' => '', |
| 90 |
'placeholder' => '', |
| 91 |
'type' => 'woo_vipps_image', |
| 92 |
'desc_tip' => false, |
| 93 |
'description' => '', |
| 94 |
'custom_attributes' => array(), |
| 95 |
); |
| 96 |
$data = wp_parse_args( $field, $defaults ); |
| 97 |
|
| 98 |
$imgid = intval($this->get_option($key)); |
| 99 |
$image = $imgid ? wp_get_attachment_image_src($imgid) : ""; |
| 100 |
|
| 101 |
ob_start(); |
| 102 |
?> |
| 103 |
<tr valign="top"> |
| 104 |
<th scope="row" class="titledesc"> |
| 105 |
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> |
| 106 |
</th> |
| 107 |
<td class="forminp"> |
| 108 |
<fieldset> |
| 109 |
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> |
| 110 |
<?php if ($image): ?> |
| 111 |
<a href="#" class="woo-vipps-image-upload"><img style="max-width: 360px; max-height: 360px" src="<?php echo $image[0]; ?>" /><span style="display:none" class='uploadtext'><?php _e('Upload image', 'woo-vipps'); ?></span></a> |
| 112 |
<a href="#" class="woo-vipps-image-remove"><?php _e('Remove image', 'woo-vipps');?></a> |
| 113 |
<?php else: ?> |
| 114 |
<a href="#" class="woo-vipps-image-upload"><img style="display:none; max-width:360px; max-height: 360px"/><span class='uploadtext'><?php _e('Upload image', 'woo-vipps'); ?></span></a> |
| 115 |
<a href="#" class="woo-vipps-image-remove" style="display:none;"><?php _e('Remove image', 'woo-vipps');?></a> |
| 116 |
<?php endif; ?> |
| 117 |
<input type="hidden" class="woo-vipps-image-input <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" value="<?php echo esc_attr($imgid); ?>" <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> /> |
| 118 |
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 119 |
</fieldset> |
| 120 |
</td> |
| 121 |
</tr> |
| 122 |
<?php |
| 123 |
|
| 124 |
return ob_get_clean(); |
| 125 |
} |
| 126 |
|
| 127 |
// Attempts to detect the current country based on the store's currency NT-2024-10-15 |
| 128 |
private function detect_country_from_currency() { |
| 129 |
$currency = get_woocommerce_currency(); |
| 130 |
switch ($currency) { |
| 131 |
case 'DKK': |
| 132 |
return 'DK'; |
| 133 |
case 'NOK': |
| 134 |
return 'NO'; |
| 135 |
case 'SEK': |
| 136 |
return 'SE'; |
| 137 |
case 'EUR': |
| 138 |
return 'FI'; |
| 139 |
default: |
| 140 |
return null; |
| 141 |
} |
| 142 |
} |
| 143 |
// Migrates the keysets to include the country setting if it's missing |
| 144 |
// This is an initial migration step to ensure the country setting is explicitly set. |
| 145 |
// We do this because we no longer want to automatically guess payment method name. NT-2024-10-15 |
| 146 |
private function migrate_keyset_with_country_detection() { |
| 147 |
$settings = get_option('woocommerce_vipps_settings', array()); |
| 148 |
if ($settings['country'] ?? false) return; // Already set, do nothing IOK 2024-10-17 |
| 149 |
|
| 150 |
// Now we are only wanting to do this with people who have already configured the plugin. These will have at least this value set: |
| 151 |
if ($settings['payment_method_name'] ?? false) { |
| 152 |
// This assumes that EUR == FI which will be correct for all users reaching this branch IOK 2024-10-17 |
| 153 |
$detected_country = $this->detect_country_from_currency(); |
| 154 |
// If we can't detect the country, there's nothing to migrate. "this cannot happen" etc. |
| 155 |
if (!$detected_country) return; |
| 156 |
|
| 157 |
$settings['country'] = $detected_country; |
| 158 |
update_option('woocommerce_vipps_settings', $settings); |
| 159 |
delete_transient('_vipps_keyset'); |
| 160 |
return; |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
public function __construct() { |
| 165 |
$this->testapiurl = 'https://apitest.vipps.no'; |
| 166 |
$this->apiurl = 'https://api.vipps.no'; |
| 167 |
|
| 168 |
$this->method_description = __('Offer Vipps or MobilePay as a payment method', 'woo-vipps'); |
| 169 |
$this->method_title = __('Vipps MobilePay','woo-vipps'); |
| 170 |
$this->title = __('Vipps MobilePay','woo-vipps'); |
| 171 |
|
| 172 |
$this->icon = plugins_url('img/vmp-logo.png',__FILE__); |
| 173 |
$this->migrate_keyset_with_country_detection(); |
| 174 |
$this->init_form_fields(); |
| 175 |
$this->init_settings(); |
| 176 |
|
| 177 |
|
| 178 |
$this->api = new VippsApi($this); |
| 179 |
|
| 180 |
$this->supports = array('products','refunds'); |
| 181 |
|
| 182 |
// we need to disallow certain types of refunds; mostly manual refunds done before capture. IOK 2026-02-24 |
| 183 |
add_action( 'woocommerce_create_refund', array($this, 'woocommerce_create_refund'), 10, 2); |
| 184 |
|
| 185 |
// We can't guarantee any particular product type being supported, so we must enumerate those we are certain about |
| 186 |
// IOK 2020-04-21 Add support for WooCommerce Product Bundles |
| 187 |
$supported_types= array('simple','variable','variation','bundle', 'yith_bundle', 'gift-card'); |
| 188 |
$this->express_checkout_supported_product_types = apply_filters('woo_vipps_express_checkout_supported_product_types', $supported_types); |
| 189 |
|
| 190 |
add_action('woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options') ); |
| 191 |
add_action('admin_init', array($this, 'add_image_upload_setting_widget')); |
| 192 |
|
| 193 |
// Capturing, refunding and cancelling the order when transitioning states: |
| 194 |
// This are the statuses for which the Vipps MobilePay plugin should try to ensure capture has been made. |
| 195 |
// Normally, this is 'processing' and 'completed', but plugins may define other statuses. IOK 2018-10-05 |
| 196 |
// It is also possible to remove 'processing' from this list. If you do, you may use it as the end-state of the |
| 197 |
// Vipps MobilePay transaction (see below in after_vipps_order_status) IOK 2018-12-05 |
| 198 |
$resultstatus = $this->get_option('result_status'); |
| 199 |
$captured_statuses = apply_filters('woo_vipps_captured_statuses', array('processing', 'completed')); |
| 200 |
$captured_statuses = array_diff($captured_statuses, array($resultstatus)); |
| 201 |
|
| 202 |
$this->captured_statuses = $captured_statuses; |
| 203 |
|
| 204 |
$non_completed_captured_statuses = array_diff($captured_statuses, array('completed')); |
| 205 |
|
| 206 |
// This ensures that funds are captured when transitioning from 'on hold' to a status where the money |
| 207 |
// should be captured, and refunded when moved from this status to cancelled or refunded |
| 208 |
foreach($captured_statuses as $capstatus) { |
| 209 |
add_action('woocommerce_order_status_' . $capstatus, array($this, 'maybe_capture_payment')); |
| 210 |
} |
| 211 |
// We will refund money on cancelled orders, but only if they are *relatively new*. This is to |
| 212 |
// avoid accidents and issues where old orders are *somehow* cancelled even though they are complete. IOK 2024-08-12 |
| 213 |
add_action('woocommerce_order_status_cancelled', array($this, 'maybe_cancel_order')); |
| 214 |
// Add a full refund if neccessary *before* woocommerce does, on priority 10 IOK 2026-01-28 |
| 215 |
add_action('woocommerce_order_status_refunded', array($this, 'maybe_refund_order'), 9, 1); |
| 216 |
|
| 217 |
// Possibly delete orders that never went anywhere |
| 218 |
add_action('woocommerce_order_status_pending_to_cancelled', array($this, 'maybe_delete_order'), 99999, 1); |
| 219 |
// Handle orders when authorized |
| 220 |
add_action('woocommerce_payment_complete', array($this, 'order_payment_complete'), 10, 1); |
| 221 |
|
| 222 |
// when an order is complete, we need to check if there is reserved amount that is not captured |
| 223 |
// if so, we need to cancel this amount PMB 2024-11-21 |
| 224 |
// nb: note very late priority - we must have captured before, please |
| 225 |
// Also for orders that have been partially or completely refunded, or need to be set to cancelled IOK 2026-01-26 |
| 226 |
add_action('woocommerce_order_status_completed', array($this, 'maybe_cancel_reserved_amount'), 99); |
| 227 |
add_action('woocommerce_order_status_refunded', array($this, 'maybe_cancel_reserved_amount'), 99, 1); |
| 228 |
add_action('woocommerce_order_status_cancelled', array($this, 'maybe_cancel_reserved_amount'), 99, 1); |
| 229 |
} |
| 230 |
|
| 231 |
// this function is called after an order is changed to complete/refunded/cancelled. It checks if there is reserved money that is not captured |
| 232 |
// if there still is money reserved, then this amount is cancelled PMB 2024-11-21 |
| 233 |
// Ensure we've updated the vipps status before calling this. IOK 2026-01-28 |
| 234 |
public function maybe_cancel_reserved_amount ($orderid) { |
| 235 |
$order = wc_get_order($orderid); |
| 236 |
if (!$order) return; |
| 237 |
if (! Vipps::is_vipps_order($order)) return false; |
| 238 |
// Cannot partially cancel legacy ecom orders |
| 239 |
if ('epayment' != $order->get_meta('_vipps_api')) return false; |
| 240 |
|
| 241 |
// Check that the normal maybe_capture_order hook has actually ran *and* done something, |
| 242 |
// it's only after this we know we have captured 'everything' so if there is anything left, |
| 243 |
// it should be cancelled. IOK 2025-05-04 |
| 244 |
// Also set in maybe_cancel and maybe_refund now - in all "final status" hooks. IOK 2026-01-28 |
| 245 |
if (! $order->get_meta('_vipps_capture_complete')) { |
| 246 |
return false; |
| 247 |
} |
| 248 |
|
| 249 |
$order_status = $order->get_status(); |
| 250 |
|
| 251 |
if ('completed' == $order_status) { |
| 252 |
// For safety, on completed orders also only want to do this for orders that have had *something* captured. IOK 2025-02-04 |
| 253 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 254 |
if ($captured < 1) { |
| 255 |
return false; |
| 256 |
} |
| 257 |
} |
| 258 |
if ('cancelled' != $order_status) { |
| 259 |
// If not in the 'cancelled' state, allow merchants that do not reserve large amounts to opt out for safety IOK 2025-02-04 |
| 260 |
if (apply_filters('woo_vipps_never_cancel_uncaptured_money', false, $order)) { |
| 261 |
return false; |
| 262 |
} |
| 263 |
} |
| 264 |
|
| 265 |
$ok = true; |
| 266 |
|
| 267 |
$remaining = intval($order->get_meta('_vipps_capture_remaining')); |
| 268 |
|
| 269 |
if ($remaining > 0) { |
| 270 |
$this->log(sprintf(__("maybe_cancel_reserved_amount we have remaining reserved after capture of total %1\$s ",'woo-vipps'), $remaining),'debug'); |
| 271 |
} |
| 272 |
|
| 273 |
$currency = $order->get_currency(); |
| 274 |
try { |
| 275 |
$ok = $this->cancel_payment($order); |
| 276 |
if ($ok && $remaining && 'completed' == $order_status) { |
| 277 |
$amount = number_format($remaining/100, 2) . " " . $currency; |
| 278 |
$note = sprintf(__('Order %1$s: %2$s is cancelled to free up the reservation in the customers bank account.', 'woo-vipps'), $orderid, $amount); |
| 279 |
$order->add_order_note($note); |
| 280 |
} |
| 281 |
} catch (Exception $e) { |
| 282 |
$ok = false; |
| 283 |
// if this happens, we just log it - we may not have an active admin |
| 284 |
$msg = sprintf(__('Was not able to cancel remaining amount for the order %1$s: %2$s','woo-vipps'), $orderid, $e->getMessage()); |
| 285 |
$order->add_order_note($msg); |
| 286 |
$this->log($msg,'error'); |
| 287 |
} |
| 288 |
|
| 289 |
// We need to update the order details after the fact. We can't fix errors here though. IOK 2024-11-22 |
| 290 |
try { |
| 291 |
$this->update_vipps_payment_details($order); |
| 292 |
} catch (Exception $e) { |
| 293 |
// noop |
| 294 |
} |
| 295 |
|
| 296 |
// we just return true from this function for now PMB 2024-11-21 |
| 297 |
// return false if we couldn't cancel reserved. IOK 2024-11-22 |
| 298 |
return $ok; |
| 299 |
} |
| 300 |
|
| 301 |
|
| 302 |
public function get_icon () { |
| 303 |
$src = $this->icon; |
| 304 |
if ($this->get_payment_method_name() == "Vipps") { |
| 305 |
$src = plugins_url('img/vipps-mark.svg',__FILE__); |
| 306 |
} else { |
| 307 |
$src = plugins_url('img/mobilepay-mark.png',__FILE__); |
| 308 |
} |
| 309 |
return '<img src="' . esc_attr($src) . '" alt="' . $this->get_payment_method_name() . '">'; |
| 310 |
} |
| 311 |
|
| 312 |
|
| 313 |
// True iff this gateway is currently in test mode. IOK 2019-08-30 |
| 314 |
public function is_test_mode() { |
| 315 |
if (VIPPS_TEST_MODE) return true; |
| 316 |
if ($this->get_option('developermode') == 'yes' && $this->get_option('testmode') == 'yes') return true; |
| 317 |
return false; |
| 318 |
} |
| 319 |
// These abstraction gets the correct client id and so forth based on whether or not test mode is on |
| 320 |
// "test mode" is now per MSN, so we accept that as an argument IOK 2023-12-19 |
| 321 |
public function apiurl ($msn="") { |
| 322 |
$msn = $msn ?? $this->get_merchant_serial(); |
| 323 |
$keyset = $this->get_keyset(); |
| 324 |
$entry = $keyset ? ($keyset[$msn] ?? null) : null; |
| 325 |
if (!$entry) { |
| 326 |
$testmode = $this->is_test_mode(); |
| 327 |
} else { |
| 328 |
$testmode = $entry['testmode']; |
| 329 |
} |
| 330 |
if ($testmode) return $this->testapiurl; |
| 331 |
return $this->apiurl; |
| 332 |
} |
| 333 |
|
| 334 |
// This returns the *current* merchant serial number. There may be more than one, for instance if the test mode is on. |
| 335 |
// IOK 2023-12-19 |
| 336 |
public function get_merchant_serial() { |
| 337 |
$merch = $this->get_option('merchantSerialNumber'); |
| 338 |
$testmerch = @$this->get_option('merchantSerialNumber_test'); |
| 339 |
if (!empty($testmerch) && $this->is_test_mode()) return $testmerch; |
| 340 |
return $merch; |
| 341 |
} |
| 342 |
|
| 343 |
// Returns a table of all the keydata of this instance, keyed on MSN. IOK 2023-12-19 |
| 344 |
public function get_keyset() { |
| 345 |
if ($this->keyset) return $this->keyset; |
| 346 |
$stored = get_transient('_vipps_keyset'); |
| 347 |
if ($stored) { |
| 348 |
return $stored; |
| 349 |
} |
| 350 |
|
| 351 |
$keyset = []; |
| 352 |
$main = $this->get_option('merchantSerialNumber'); |
| 353 |
if ($main) { |
| 354 |
$data = ['client_id'=>$clientid=$this->get_option('clientId'), |
| 355 |
'client_secret' => $this->get_option('secret'), |
| 356 |
'sub_key'=>$this->get_option('Ocp_Apim_Key_eCommerce'), |
| 357 |
'country' => $this->get_option('country'), |
| 358 |
'gw' => 'vipps' |
| 359 |
]; |
| 360 |
if (! in_array(false, array_map('boolval', array_values($data)))) { |
| 361 |
$data['testmode'] = 0; // Must add after |
| 362 |
$keyset[$main] = $data; |
| 363 |
} |
| 364 |
} |
| 365 |
|
| 366 |
$test = @$this->get_option('merchantSerialNumber_test'); |
| 367 |
$testmode = @$this->get_option('testmode'); |
| 368 |
if ($testmode === 'yes' && $test) { |
| 369 |
$data = [ |
| 370 |
'client_id'=>$clientid=$this->get_option('clientId_test'), |
| 371 |
'client_secret' => $this->get_option('secret_test'), |
| 372 |
'sub_key'=>$this->get_option('Ocp_Apim_Key_eCommerce_test'), |
| 373 |
'country' => $this->get_option('country'), |
| 374 |
'gw' => 'vipps' |
| 375 |
]; |
| 376 |
|
| 377 |
if (! in_array(false, array_map('boolval', array_values($data)))) { |
| 378 |
$data['testmode'] = 1; |
| 379 |
$keyset[$test] = $data; |
| 380 |
} |
| 381 |
} |
| 382 |
|
| 383 |
$this->keyset = $keyset; |
| 384 |
set_transient('_vipps_keyset', $keyset, DAY_IN_SECONDS); |
| 385 |
return $keyset; |
| 386 |
} |
| 387 |
|
| 388 |
// Get and show any keysets from the Vipps Recurring plugin too IOK 2024-11-29 |
| 389 |
private function get_recurring_keysets () { |
| 390 |
$res = []; |
| 391 |
$settings = get_option( 'woocommerce_vipps_recurring_settings' ); |
| 392 |
if (empty($settings)) return $res; |
| 393 |
|
| 394 |
$country = $this->get_option('country'); |
| 395 |
|
| 396 |
$client_id = $settings["client_id"] ?? ""; |
| 397 |
$client_secret = $settings["secret_key"] ?? ""; |
| 398 |
$subscription_key = $settings["subscription_key"] ?? ""; |
| 399 |
$merchant_serial_number = $settings["merchant_serial_number"] ?? ""; |
| 400 |
if ($merchant_serial_number && $client_id && $client_secret && $subscription_key ) { |
| 401 |
$res[$merchant_serial_number] = ['client_id' => $client_id, 'client_secret' => $client_secret, 'sub_key' => $subscription_key, 'country' => $country, 'testmode' => 0, 'gw'=>'vipps_recurring']; |
| 402 |
} |
| 403 |
|
| 404 |
$client_id = $settings["test_client_id"] ?? ""; |
| 405 |
$client_secret = $settings["test_secret_key"] ?? ""; |
| 406 |
$subscription_key = $settings["test_subscription_key"] ?? ""; |
| 407 |
$merchant_serial_number = $settings["test_merchant_serial_number"] ?? ""; |
| 408 |
if ($merchant_serial_number && $client_id && $client_secret && $subscription_key ) { |
| 409 |
$res[$merchant_serial_number] = ['client_id' => $client_id, 'client_secret' => $client_secret, 'sub_key' => $subscription_key, 'country' => $country, 'testmode' => 1, 'gw'=>'vipps_recurring']; |
| 410 |
} |
| 411 |
return $res; |
| 412 |
} |
| 413 |
|
| 414 |
// Return all webhooks for our MSNs |
| 415 |
public function get_webhooks_from_vipps () { |
| 416 |
$keys = $this->get_keyset(); |
| 417 |
$hooks = []; |
| 418 |
foreach($keys as $msn=>$data) { |
| 419 |
try { |
| 420 |
$hooks[$msn] = $this->api->get_webhooks($msn); |
| 421 |
} catch (Exception $e) { |
| 422 |
$this->log(sprintf(__('Could not get webhooks for Merchant Serial Number %1$s: %2$s', 'woo-vipps'), $msn, $e->getMessage()), 'error'); |
| 423 |
$hooks[$msn]=[]; |
| 424 |
} |
| 425 |
} |
| 426 |
return $hooks; |
| 427 |
} |
| 428 |
|
| 429 |
|
| 430 |
// The rest of the settings gets the correct client id, secret, sub key and order prefix based on the MSN. |
| 431 |
public function get_clientid($msn="") { |
| 432 |
if (!$msn) $msn = $this->get_merchant_serial(); |
| 433 |
$keyset = $this->get_keyset(); |
| 434 |
if (!isset($keyset[$msn])) return false; |
| 435 |
return $keyset[$msn]['client_id']; |
| 436 |
} |
| 437 |
public function get_secret($msn="") { |
| 438 |
if (!$msn) $msn = $this->get_merchant_serial(); |
| 439 |
$keyset = $this->get_keyset(); |
| 440 |
if (!isset($keyset[$msn])) return false; |
| 441 |
return $keyset[$msn]['client_secret']; |
| 442 |
} |
| 443 |
public function get_key($msn="") { |
| 444 |
if (!$msn) $msn = $this->get_merchant_serial(); |
| 445 |
$keyset = $this->get_keyset(); |
| 446 |
if (!isset($keyset[$msn])) return false; |
| 447 |
return $keyset[$msn]['sub_key']; |
| 448 |
} |
| 449 |
public function get_country($msn="") { |
| 450 |
if (!$msn) $msn = $this->get_merchant_serial(); |
| 451 |
$keyset = $this->get_keyset(); |
| 452 |
if (!isset($keyset[$msn])) return false; |
| 453 |
return $keyset[$msn]['country']; |
| 454 |
} |
| 455 |
|
| 456 |
public function get_orderprefix() { |
| 457 |
$prefix = $this->get_option('orderprefix'); |
| 458 |
return $prefix; |
| 459 |
} |
| 460 |
|
| 461 |
// We did shenanigans here earlier, we don't have to do that anymore. IOK 2022-12-09 |
| 462 |
public function get_return_url($order=null) { |
| 463 |
$url = parent::get_return_url($order); |
| 464 |
return $url; |
| 465 |
} |
| 466 |
|
| 467 |
|
| 468 |
// Delete express checkout orders with no customer information - these were abandonend before the app started. |
| 469 |
// IOK 2019-08-26 |
| 470 |
public function maybe_delete_order ($orderid) { |
| 471 |
$order = wc_get_order($orderid); |
| 472 |
if (!$order) return; |
| 473 |
if (! Vipps::is_vipps_order($order)) return false; |
| 474 |
$express = $order->get_meta('_vipps_express_checkout'); |
| 475 |
if (!$express) return false; |
| 476 |
$email = $order->get_billing_email(); |
| 477 |
if ($email) return false; |
| 478 |
|
| 479 |
// Only delete if we have to |
| 480 |
if ($this->get_option('deletefailedexpressorders') != 'yes') return false; |
| 481 |
// Mark this order that an order that wasn't completed with any user info - it can be deleted. IOK 2019-11-13 |
| 482 |
$order->update_meta_data('_vipps_delendum',1); |
| 483 |
$order->save(); |
| 484 |
return true; |
| 485 |
} |
| 486 |
|
| 487 |
|
| 488 |
// Return the status to use after return from Vipps MobilePay for orders that are not both "virtual" and "downloadable". |
| 489 |
// These orders are *not* complete, and payment is *not* captured, which is why the default status is 'on-hold'. |
| 490 |
// If you use custom order statuses, or if you don't capture on 'processing' - see filter 'woo_vipps_captured_statuses' - |
| 491 |
// you can instead use 'processing' here - which is much nicer. |
| 492 |
// If you do so, remember to capture *before* shipping is done on the order - if you send the package and then do 'complete', |
| 493 |
// the capture may fail. IOK 2018-12-05 |
| 494 |
// |
| 495 |
// IOK As of 2023-12-22, the default is now 'processing', since this is more in line with what other gateways are using, |
| 496 |
// what other integrations and plugins expect, the most popular choice by users; and because of the fact that "on-hold" is |
| 497 |
// normally used to indicate "a problem with the order". Not being able to capture a reserved order has to my knowledge at this |
| 498 |
// point only happened once, in 2018, with a completely different api and backend. |
| 499 |
public function after_vipps_order_status($order=null) { |
| 500 |
// Revert to on-hold if the user tries to set a payment status that is a 'captured' status IOK 2024-01-25 |
| 501 |
$defaultstatus = 'on-hold'; |
| 502 |
|
| 503 |
$chosen = $this->get_option('result_status'); |
| 504 |
$newstatus = apply_filters('woo_vipps_after_vipps_order_status', $chosen, $order); |
| 505 |
|
| 506 |
if (in_array($newstatus, $this->captured_statuses)){ |
| 507 |
$this->log(sprintf(__("Cannot use %1\$s as status for non-autocapturable orders: payment is captured on this status. See the woo_vipps_captured_statuses-filter.",'woo-vipps'), $newstatus),'debug'); |
| 508 |
return $defaultstatus; |
| 509 |
} |
| 510 |
return $newstatus; |
| 511 |
} |
| 512 |
|
| 513 |
// Create callback urls' using WC's callback API in a way that works with Vipps MobilePay callbacks and both pretty and not so pretty urls. |
| 514 |
private function make_callback_urls($forwhat,$token='', $reference=0) { |
| 515 |
// Passing the token as GET arguments, as the Authorize header is stripped. IOK 2018-06-13 |
| 516 |
// This applies to Ecom, Checkout and Express Checkout callbacks. For epayment, we instead need to use |
| 517 |
// the webhook api, which is altogether different. IOK 2023-12-19 |
| 518 |
$url = home_url("/", 'https'); |
| 519 |
$queryargs = []; |
| 520 |
if ($token) $queryargs['tk']=$token; |
| 521 |
if ($reference) $queryargs['id']=$reference; |
| 522 |
|
| 523 |
// HTTPS required. IOK 2018-05-18 |
| 524 |
// If the user for some reason hasn't enabled pretty links, fall back to ancient version. IOK 2018-04-24 |
| 525 |
if ( !get_option('permalink_structure')) { |
| 526 |
$queryargs['wc-api'] = $forwhat; |
| 527 |
} else { |
| 528 |
$url = trailingslashit(home_url("wc-api/$forwhat", 'https')); |
| 529 |
} |
| 530 |
// And we need to add an empty "callback" query arg as the very last arg to receive the actual callback. |
| 531 |
// We can't use add_query_arg for that, as an empty argument will remove the equals-sign. |
| 532 |
$callbackurl = add_query_arg($queryargs, $url) . "&callback="; |
| 533 |
return $callbackurl; |
| 534 |
} |
| 535 |
|
| 536 |
// Webhook callbacks do not pass GET arguments at all, but do provide an X-Vipps-Authorization header for verification. IOK 2023-12-19 |
| 537 |
public function webhook_callback_url () { |
| 538 |
$url = home_url("/", 'https'); |
| 539 |
$queryargs = ['callback'=>'webhook']; |
| 540 |
$forwhat = 'wc_gateway_vipps'; // Same callback as for ecom, checkout, express checkout |
| 541 |
// HTTPS required. IOK 2018-05-18 |
| 542 |
// If the user for some reason hasn't enabled pretty links, fall back to ancient version. IOK 2018-04-24 |
| 543 |
if ( !get_option('permalink_structure')) { |
| 544 |
$queryargs['wc-api'] = $forwhat; |
| 545 |
} else { |
| 546 |
$url = trailingslashit(home_url("wc-api/$forwhat", 'https')); |
| 547 |
} |
| 548 |
$callbackurl = add_query_arg($queryargs, $url); |
| 549 |
return $callbackurl; |
| 550 |
} |
| 551 |
|
| 552 |
|
| 553 |
// The main payment callback |
| 554 |
public function payment_callback_url ($token='', $reference=0) { |
| 555 |
return $this->make_callback_urls('wc_gateway_vipps',$token, $reference); |
| 556 |
} |
| 557 |
public function shipping_details_callback_url($token='',$reference=0) { |
| 558 |
return $this->make_callback_urls('vipps_shipping_details',$token,$reference); |
| 559 |
} |
| 560 |
// Callback for the consetn removal callback. Must use template redirect directly, because wc-api doesn't handle DELETE. |
| 561 |
// IOK 2018-05-18 |
| 562 |
public function consent_removal_callback_url () { |
| 563 |
$queryargs = []; |
| 564 |
$url = home_url("/", 'https'); |
| 565 |
if ( !get_option('permalink_structure')) { |
| 566 |
$queryargs['vipps-consent-removal']=1; |
| 567 |
} else { |
| 568 |
$url = trailingslashit(home_url('vipps-consent-removal', 'https')); |
| 569 |
} |
| 570 |
// And we need to add an empty "callback" query arg as the very last arg to receive the actual callback. |
| 571 |
// We can't use add_query_arg for that, as an empty argument will remove the equals-sign. |
| 572 |
return add_query_arg($queryargs, $url) . "&callback="; |
| 573 |
} |
| 574 |
|
| 575 |
// Allow user to select the template to be used for the special Vipps MobilePay pages. IOK 2020-02-17 |
| 576 |
public function get_theme_page_templates() { |
| 577 |
if (!$this->page_templates) { |
| 578 |
$choices = array('' => __('Use default template', 'woo-vipps')); |
| 579 |
foreach(wp_get_theme()->get_page_templates() as $filename=>$name) { |
| 580 |
$choices[$filename]=$name; |
| 581 |
} |
| 582 |
$this->page_templates = $choices; |
| 583 |
} |
| 584 |
return $this->page_templates; |
| 585 |
} |
| 586 |
|
| 587 |
// We can't use get_pages to get a default list of pages for our settings, because it triggers |
| 588 |
// actions that can be used by other plugins. Therefore we must use the database directly and cache the results. IOK 2023-08-22 |
| 589 |
public function get_pagelist () { |
| 590 |
if (!$this->page_list) { |
| 591 |
global $wpdb; |
| 592 |
$page_list = array(''=>__('Use a simulated page (default)', 'woo-vipps')); |
| 593 |
foreach($wpdb->get_results("SELECT ID,post_title FROM {$wpdb->prefix}posts WHERE post_type='page' and post_status='publish'") as $page) { |
| 594 |
$page_list[$page->ID] = $page->post_title; |
| 595 |
} |
| 596 |
$this->page_list = $page_list; |
| 597 |
} |
| 598 |
return $this->page_list; |
| 599 |
} |
| 600 |
|
| 601 |
// Check to see if the product in question can be bought with express checkout IOK 2018-12-04 |
| 602 |
public function product_supports_express_checkout($product) { |
| 603 |
// IOK 2023-12-12 Can only support express checkout for Vipps - not MobilePay (yet!) |
| 604 |
// IOK 2025-09-01 Now supports mobilepay |
| 605 |
return apply_filters('woo_vipps_product_supports_express_checkout', $this->product_supports_checkout($product), $product); |
| 606 |
} |
| 607 |
|
| 608 |
// Checkout and Express Checkout are very similarily restricted because they both replace the standard |
| 609 |
// Woo Checkout page, but express checkout is even more restricted, so we need to separate out the commonalities. IOK 2024-01-11 |
| 610 |
public function product_supports_checkout($product) { |
| 611 |
$type = $product->get_type(); |
| 612 |
$ok = in_array($type, $this->express_checkout_supported_product_types); |
| 613 |
$ok = apply_filters('woo_vipps_product_supports_checkout',$ok,$product); |
| 614 |
return $ok; |
| 615 |
} |
| 616 |
|
| 617 |
// Almost the same as express checkout - unfortunately not *entirely* the same. IOK 2024-01-11 |
| 618 |
public function cart_supports_checkout($cart=null) { |
| 619 |
if (!$cart) $cart = WC()->cart; |
| 620 |
if (!$cart) return false; |
| 621 |
# Not supported by Vipps MobilePay |
| 622 |
if ($cart->cart_contents_total <= 0) return false; |
| 623 |
|
| 624 |
$supports = true; |
| 625 |
foreach($cart->get_cart() as $key=>$val) { |
| 626 |
$prod = $val['data']; |
| 627 |
if (!is_a($prod, 'WC_Product')) continue; |
| 628 |
$product_supported = $this->product_supports_checkout($prod); |
| 629 |
if (!$product_supported) { |
| 630 |
$supports = false; |
| 631 |
break; |
| 632 |
} |
| 633 |
} |
| 634 |
$supports = apply_filters('woo_vipps_cart_supports_checkout', $supports, $cart); |
| 635 |
return $supports; |
| 636 |
} |
| 637 |
|
| 638 |
// Check to see if the cart passed (or the global one) can be bought with express checkout IOK 2018-12-04 |
| 639 |
public function cart_supports_express_checkout($cart=null) { |
| 640 |
if (!$cart) $cart = WC()->cart; |
| 641 |
$supports = true; |
| 642 |
if (!$cart) return $supports; |
| 643 |
# Not supported by Vipps MobilePay |
| 644 |
if ($cart->cart_contents_total <= 0) return false; |
| 645 |
|
| 646 |
foreach($cart->get_cart() as $key=>$val) { |
| 647 |
$prod = $val['data']; |
| 648 |
if (!is_a($prod, 'WC_Product')) continue; |
| 649 |
$product_supported = $this->product_supports_express_checkout($prod); |
| 650 |
if (!$product_supported) { |
| 651 |
$supports = false; |
| 652 |
break; |
| 653 |
} |
| 654 |
} |
| 655 |
$supports = apply_filters('woo_vipps_cart_supports_express_checkout', $supports, $cart); |
| 656 |
return $supports; |
| 657 |
} |
| 658 |
|
| 659 |
// True if "Express checkout" should be displayed IOK 2018-06-18 |
| 660 |
public function show_express_checkout() { |
| 661 |
if (!$this->express_checkout_available()) return false; |
| 662 |
$show = ($this->enabled == 'yes') && ($this->get_option('cartexpress') == 'yes') ; |
| 663 |
$show = $show && $this->cart_supports_express_checkout(); |
| 664 |
|
| 665 |
// Earlier, we disabled this if Checkout was active; but we will now respect the setting in all |
| 666 |
// cases. Also, there is a filter. IOK 2026-02-19 |
| 667 |
|
| 668 |
return apply_filters('woo_vipps_show_express_checkout', $show); |
| 669 |
} |
| 670 |
|
| 671 |
public function show_login_with_vipps() { |
| 672 |
return false; |
| 673 |
} |
| 674 |
|
| 675 |
// Called when orders reach the 'refunded' status. We'll add a complete refund and note that any rest is to be cancelled. |
| 676 |
public function maybe_refund_order($order_id) { |
| 677 |
$order = wc_get_order($order_id); |
| 678 |
if (! Vipps::is_vipps_order($order)) return false; |
| 679 |
try { |
| 680 |
$order = $this->update_vipps_payment_details($order); |
| 681 |
} catch (Exception $e) { |
| 682 |
//Do nothing with this for now |
| 683 |
$this->log(__("Error getting payment details before doing refund: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 684 |
} |
| 685 |
$payment = $this->check_payment_status($order); |
| 686 |
if ($payment == 'initiated' || $payment == 'cancelled') { |
| 687 |
return true; // Can't refund these |
| 688 |
} |
| 689 |
|
| 690 |
// This will create + process a refund for the captured amount (if any). IOK 2026-01-26 |
| 691 |
// We always run this since woo will create a manual refund on this status change: we want the refund to be through our gw instead. LP 2026-06-10 |
| 692 |
$this->wc_order_fully_refunded ($order_id); |
| 693 |
|
| 694 |
// In any case, note that this order is ready for cancellation - we don't actually do this here anymore |
| 695 |
$order->update_meta_data('_vipps_capture_complete',true); |
| 696 |
$order->save(); |
| 697 |
} |
| 698 |
|
| 699 |
// Called when orders reach the 'cancelled'-status. When this happens, orders will be *refunded* |
| 700 |
// when they have been captured, but for added safety, this is only done when the orders are relatively new. |
| 701 |
public function maybe_cancel_order($order_id) { |
| 702 |
$order = wc_get_order($order_id); |
| 703 |
if (! Vipps::is_vipps_order($order)) return false; |
| 704 |
|
| 705 |
try { |
| 706 |
$order = $this->update_vipps_payment_details($order); |
| 707 |
} catch (Exception $e) { |
| 708 |
//Do nothing with this for now |
| 709 |
$this->log(__("Error getting payment details before doing cancel: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 710 |
} |
| 711 |
|
| 712 |
$payment = $this->check_payment_status($order); |
| 713 |
if ($payment == 'initiated' || $payment == 'cancelled') { |
| 714 |
return true; // Can't cancel these |
| 715 |
} |
| 716 |
|
| 717 |
$days_threshold = apply_filters('woo_vipps_cancel_refund_days_threshold', 30); |
| 718 |
$order_date = $order->get_date_created(); |
| 719 |
$days_since_order = (time() - $order_date->getTimestamp()) / (60 * 60 * 24); |
| 720 |
|
| 721 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 722 |
$vippsstatus = $order->get_meta('_vipps_status'); |
| 723 |
|
| 724 |
// If we have captured some funds, we must first create a refund for the amount we've captured. |
| 725 |
// However, we will only do this if the order is relatively fresh, to avoid accidentally refunding |
| 726 |
// old orders. |
| 727 |
if ($captured > 0 || $vippsstatus == 'SALE') { |
| 728 |
// If this is true then the order is *too old to refund* which would happen on maybe_cancel_payment. |
| 729 |
// add a note instead. |
| 730 |
if ($days_since_order > $days_threshold) { |
| 731 |
$note = sprintf(__('Order with captured funds older than %d days cancelled - because the order is this old, it will not be automatically refunded at Vipps. Manual refund may be required.', 'woo-vipps'), $days_threshold); |
| 732 |
$order->add_order_note($note); |
| 733 |
// Add an admin notice in case this is interactive |
| 734 |
$msg = sprintf(__("Could not cancel %1\$s payment", 'woo-vipps'), $this->get_payment_method_name()); |
| 735 |
$this->adminerr(__('Order', 'woo-vipps') . " " . $order->get_id() . ": " . $note); |
| 736 |
$order->save(); |
| 737 |
Vipps::instance()->store_admin_notices(); |
| 738 |
return false; |
| 739 |
} |
| 740 |
// This will create + process a refund for the captured amount. IOK 2026-01-26 |
| 741 |
$this->wc_order_fully_refunded ($order_id); |
| 742 |
} |
| 743 |
// In any case, note that this order is ready for cancellation - we don't actually do this here anymore |
| 744 |
$order->update_meta_data('_vipps_capture_complete',true); |
| 745 |
$order->save(); |
| 746 |
} |
| 747 |
|
| 748 |
// IOK 2024-09-01 In general, we can refund most Vipps Mobilepay orders through the api, |
| 749 |
// however, this is not the case for the Bank Transfer method available through Vipps Checkout. |
| 750 |
public function can_refund_order( $order ) { |
| 751 |
$method = $order->get_meta('_vipps_api'); |
| 752 |
switch ($method) { |
| 753 |
case 'banktransfer': |
| 754 |
return false; |
| 755 |
break; |
| 756 |
case 'epayment': |
| 757 |
return true; |
| 758 |
break; |
| 759 |
// Default is old-style ecom v2. |
| 760 |
default: |
| 761 |
return true; |
| 762 |
break; |
| 763 |
} |
| 764 |
} |
| 765 |
|
| 766 |
// This is ran in woocommerce_order_status_refunded *before* woos own wc_order_fully_refunded (priority 9) |
| 767 |
// so that we can create a through-the-gateway refund for this if neccessary. That way, *our* logic for refunds occur |
| 768 |
// instead of the normal woo logic. IOK 2026-04-16 |
| 769 |
public function wc_order_fully_refunded ($orderid) { |
| 770 |
$order = wc_get_order($orderid); |
| 771 |
if (! Vipps::is_vipps_order($order)) return false; |
| 772 |
|
| 773 |
// First check to see if we actually need to refund anything now IOK 2026-02-16 |
| 774 |
$max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded() ); |
| 775 |
if ( ! $max_refund ) { |
| 776 |
return; |
| 777 |
} |
| 778 |
|
| 779 |
// IOK 2019-10-03 it is now possible to do capture via other tools than Woo, so we must now first check to see if |
| 780 |
// the order is capturable by getting full payment details. |
| 781 |
try { |
| 782 |
$order = $this->update_vipps_payment_details($order); |
| 783 |
} catch (Exception $e) { |
| 784 |
//Do nothing with this for now |
| 785 |
$this->log(__("Error getting payment details before doing refund: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 786 |
} |
| 787 |
|
| 788 |
$data = []; |
| 789 |
$data['amount'] = $max_refund; |
| 790 |
$data['reason'] = __( 'Order fully refunded.', 'woocommerce' ); |
| 791 |
$data['order_id'] = $orderid; |
| 792 |
$data['refund_payment'] = true; // This should call our "process_refund" instead of adding a manual refund |
| 793 |
|
| 794 |
// IOK 2026-04-17 Should be all remaining un-refunded line items |
| 795 |
$line_items = apply_filters('woo_vipps_order_fully_refunded_line_items', $this->get_remaining_refundable_line_items($order), $order); |
| 796 |
$data['line_items'] = $line_items; |
| 797 |
|
| 798 |
wc_switch_to_site_locale(); |
| 799 |
$the_refund = wc_create_refund($data); |
| 800 |
wc_restore_locale(); |
| 801 |
if (is_wp_error($the_refund)) { |
| 802 |
$refund_thru_gateway = false; |
| 803 |
$msg = $the_refund->get_error_message(); |
| 804 |
$order->add_order_note(sprintf(__("Error when refunding payment through %1\$s:", 'woo-vipps'), $this->get_payment_method_name()) . ' ' . $msg); |
| 805 |
$order->save(); |
| 806 |
$this->adminerr($msg); |
| 807 |
} |
| 808 |
return true; |
| 809 |
} |
| 810 |
|
| 811 |
|
| 812 |
// IOK 2026-04-16 Build wc_create_refund() line_items for all remaining refundable order items - used to provide line-item info for the refund to be processed when |
| 813 |
// "fully refunded" is done. |
| 814 |
// [ $item_id => ['qty' => 1, 'refund_total' => '100.00', 'refund_tax' => [ 1 => '25.00' ], * ], ... ] |
| 815 |
// Includes line items, fees and shipping. For fees/shipping, qty is set to 0. |
| 816 |
private function get_remaining_refundable_line_items( WC_Order $order ) { |
| 817 |
$items = $order->get_items( array( 'line_item', 'fee', 'shipping' )); |
| 818 |
|
| 819 |
$new_refund_line_items = []; |
| 820 |
foreach($items as $item_id => $item) { |
| 821 |
$new_refund_line = []; |
| 822 |
|
| 823 |
// Calculate remaining tax for this line item; by tax id. The shape is [ [total] => [tax_id => value_for_this_tax_id] ]. |
| 824 |
// We want the tax id and the value. |
| 825 |
$tax_data = wc_tax_enabled() ? $item->get_taxes() : false; |
| 826 |
$remaining_tax = []; |
| 827 |
if ($tax_data) { |
| 828 |
foreach($tax_data['total'] as $tax_id => $value) { |
| 829 |
if ('' === $value) continue; // don't add empty string as tax value, fatal crash. LP 2026-06-08 |
| 830 |
$remaining_tax[$tax_id] = $value; |
| 831 |
} |
| 832 |
} |
| 833 |
// We can then subtract the tax already refunded for each of these items. |
| 834 |
foreach($remaining_tax as $tax_id => $current) { |
| 835 |
$refunded = $order->get_tax_refunded_for_item($item_id, $tax_id, $item->get_type()); |
| 836 |
$remaining = wc_format_decimal($current-$refunded); |
| 837 |
if ($remaining > 0) { |
| 838 |
$remaining_tax[$tax_id] = wc_format_decimal($current-$refunded); |
| 839 |
} else { |
| 840 |
unset($remaining_tax[$tax_id]); |
| 841 |
} |
| 842 |
} |
| 843 |
|
| 844 |
// Then the quantity |
| 845 |
$qty = (int) $item->get_quantity(); |
| 846 |
$refunded_quantity = abs((int) $order->get_qty_refunded_for_item($item_id, $item->get_type())); // Documented to be positive since 3.0, seems to be actually negative. |
| 847 |
$remaining_quantity = $qty-$refunded_quantity; |
| 848 |
|
| 849 |
$total = $item->get_total(); |
| 850 |
$refunded_total = $order->get_total_refunded_for_item($item_id, $item->get_type()); // A positive value |
| 851 |
$remaining_total = wc_format_decimal($total-$refunded_total); |
| 852 |
|
| 853 |
|
| 854 |
if ($remaining_quantity>0 || !empty($remaining_tax) || $remaining_total > 0) { |
| 855 |
$new_refund_line['qty'] = max(0,$remaining_quantity); |
| 856 |
$new_refund_line['refund_tax'] = $remaining_tax; |
| 857 |
$new_refund_line['refund_total'] = $remaining_total; |
| 858 |
$new_refund_line_items[$item_id] = $new_refund_line; |
| 859 |
} |
| 860 |
|
| 861 |
} |
| 862 |
|
| 863 |
return $new_refund_line_items; |
| 864 |
|
| 865 |
} |
| 866 |
|
| 867 |
|
| 868 |
// This is for orders that are 'reserved' at Vipps but could actually be captured at once because |
| 869 |
// they don't require payment. So we try to capture. IOK 2020-09-22 |
| 870 |
// do NOT call this unless the order is 'reserved' at Vipps! |
| 871 |
protected function maybe_complete_payment($order) { |
| 872 |
if (! Vipps::is_vipps_order($order)) return false; |
| 873 |
if ($order->needs_processing()) return false; // No auto-capture for orders needing processing |
| 874 |
// IOK 2018-10-03 when implementing partial capture, this must be modified. |
| 875 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 876 |
$vippsstatus = $order->get_meta('_vipps_status'); |
| 877 |
if ($captured || $vippsstatus == 'SALE') { |
| 878 |
return true; |
| 879 |
} |
| 880 |
$ok = 0; |
| 881 |
try { |
| 882 |
$ok = $this->capture_payment($order); |
| 883 |
$order->add_order_note(sprintf(__('Payment automatically captured at %1$s for order not needing processing','woo_vipps'), $this->get_payment_method_name())); |
| 884 |
} catch (Exception $e) { |
| 885 |
$order->add_order_note(sprintf(__('Order does not need processing, but payment could not be captured at %1$s:','woo_vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage()); |
| 886 |
} |
| 887 |
if (!$ok) return false; |
| 888 |
$order->save(); |
| 889 |
return true; |
| 890 |
} |
| 891 |
|
| 892 |
// This filter runs on *all* refunds, including manual refunds. Its main job here is to |
| 893 |
// disallow creating manual refunds on orders that have not been captured yet, since this makes the capture/cancel logic |
| 894 |
// very confusing |
| 895 |
public function woocommerce_create_refund ($refund, $args) { |
| 896 |
$order_id = intval($args['order_id'] ?? 0); |
| 897 |
$order = $order_id ? wc_get_order( $order_id ) : null; |
| 898 |
if ( ! $order ) return; |
| 899 |
if (! Vipps::is_vipps_order($order)) return; |
| 900 |
// This is for manual refunds only IOK 2026-02-24 |
| 901 |
if (!($args['refund_payment'] ?? false)) { |
| 902 |
try { |
| 903 |
$order = $this->update_vipps_payment_details($order); |
| 904 |
} catch (Exception $e) { |
| 905 |
//Do nothing with this for now |
| 906 |
$this->log(__("Error getting payment details before doing refund: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 907 |
} |
| 908 |
// This would be *per order line* in the fulfilment branch. IOK 2026-02-24 FIXME |
| 909 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 910 |
if (!$captured) { |
| 911 |
$msg = sprintf(__("Order %2\$d: It is not possible to create a manual refund for a %1\$s order before it has been captured - doing so makes it impossible to track how much to capture and how much to release. If you create the refund through %1\$s, a note will be made internally so that the amount to be refunded will *not* be captured - please do this instead if possible. Otherwise, capture the order then refund either manually or through %1\$s", 'woo-vipps'), $this->get_payment_method_name(), $order_id); |
| 912 |
throw new Exception($msg); |
| 913 |
} |
| 914 |
} |
| 915 |
} |
| 916 |
|
| 917 |
// This is the Woocommerce refund api called by the "Refund" actions. IOK 2018-05-11 |
| 918 |
public function process_refund($orderid,$amount=null,$reason='') { |
| 919 |
$order = wc_get_order($orderid); |
| 920 |
|
| 921 |
$currency = $order->get_currency(); |
| 922 |
|
| 923 |
try { |
| 924 |
$order = $this->update_vipps_payment_details($order); |
| 925 |
} catch (Exception $e) { |
| 926 |
//Do nothing with this for now |
| 927 |
$this->log(__("Error getting payment details before doing refund: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 928 |
} |
| 929 |
|
| 930 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 931 |
$to_refund = intval($order->get_meta('_vipps_refund_remaining')); |
| 932 |
|
| 933 |
// No funds captured, by epayment can do cancel of partial capture, so let's note that we are not to capture this. |
| 934 |
if (!$captured) { |
| 935 |
if ('epayment' != $order->get_meta('_vipps_api')) { |
| 936 |
return new WP_Error('Vipps', sprintf(__("Cannot refund through %1\$s - the payment has not been captured yet.", 'woo-vipps'), $this->get_payment_method_name())); |
| 937 |
} |
| 938 |
if ($amount > $order->get_total()) { |
| 939 |
return new WP_Error('Vipps', sprintf(__("Cannot refund through %1\$s - the refund amount is too large.", 'woo-vipps'), $this->get_payment_method_name())); |
| 940 |
} |
| 941 |
$msg = sprintf(__('The money for order %1$d has not been captured, only reserved. %2$s %3$s of the reserved funds will be released when the order is set to complete.', 'woo-vipps'), $orderid, $amount, $currency); |
| 942 |
$this->log($msg, 'info'); |
| 943 |
$uncapturable = round($amount * 100) + intval($order->get_meta('_vipps_noncapturable')); |
| 944 |
|
| 945 |
$order->update_meta_data('_vipps_noncapturable', $uncapturable); |
| 946 |
$order->add_order_note($msg); |
| 947 |
$order->save(); |
| 948 |
return true; |
| 949 |
} |
| 950 |
|
| 951 |
if ($amount*100 > $to_refund) { |
| 952 |
return new WP_Error('Vipps', sprintf(__("Cannot refund through %1\$s - the refund amount is too large.", 'woo-vipps'), $this->get_payment_method_name())); |
| 953 |
} |
| 954 |
$ok = 0; |
| 955 |
|
| 956 |
// Specialcase zero, because Vipps treats this as the entire amount IOK 2021-09-14 |
| 957 |
if (is_numeric($amount) && $amount == 0) { |
| 958 |
$order->add_order_note($amount . ' ' . $currency . ' ' . sprintf(__(" refunded through %1\$s:",'woo-vipps'), Vipps::CompanyName()) . ' ' . $reason); |
| 959 |
return true; |
| 960 |
} |
| 961 |
|
| 962 |
try { |
| 963 |
$ok = $this->refund_payment($order,$amount); |
| 964 |
} catch (TemporaryVippsApiException $e) { |
| 965 |
$this->log(sprintf(__('Could not refund %1$s payment for order id:', 'woo-vipps'), $this->get_payment_method_name()) . ' ' . $orderid . "\n" .$e->getMessage(),'error'); |
| 966 |
return new WP_Error('Vipps',sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), Vipps::CompanyName()) . ' ' . $e->getMessage()); |
| 967 |
} catch (Exception $e) { |
| 968 |
$msg = sprintf(__('Could not refund %1$s payment','woo-vipps'), Vipps::CompanyName()) . ' ' . $e->getMessage(); |
| 969 |
$order->add_order_note($msg); |
| 970 |
$this->log($msg,'error'); |
| 971 |
return new WP_Error('Vipps',$msg); |
| 972 |
} |
| 973 |
|
| 974 |
if ($ok) { |
| 975 |
$order->add_order_note($amount . ' ' . $currency . ' ' . sprintf(__(" refunded through %1\$s:",'woo-vipps'), Vipps::CompanyName()) . ' ' . $reason); |
| 976 |
} |
| 977 |
return $ok; |
| 978 |
} |
| 979 |
|
| 980 |
// Detect default payment method based on country code NT-2024-10-15 |
| 981 |
public function detect_default_payment_method_from_country($country_code) { |
| 982 |
// Default to MobilePay |
| 983 |
$payment_method_name = 'MobilePay'; |
| 984 |
// Default to Vipps if country is Norway or Sweden |
| 985 |
if($country_code == 'NO' || $country_code == 'SE') { |
| 986 |
$payment_method_name = 'Vipps'; |
| 987 |
} |
| 988 |
return $payment_method_name; |
| 989 |
} |
| 990 |
|
| 991 |
// Returns true iff this is a store where Vipps will allow external payment methods. |
| 992 |
// Currently this is only Finland, and only Klarna is supported. We need to call this like so because |
| 993 |
// most of woocommerce will not be initialized when we need this info. IOK 2024-05-28 |
| 994 |
public function allow_external_payments_in_checkout() { |
| 995 |
$store_location= wc_get_base_location(); |
| 996 |
$store_country = $store_location['country'] ?? ''; |
| 997 |
$finland = (get_woocommerce_currency() == "EUR" && $store_country == "FI"); |
| 998 |
$norway = (get_woocommerce_currency() == "NOK" && $store_country == "NO"); |
| 999 |
$sweden = (get_woocommerce_currency() == "SEK" && $store_country == "SE"); |
| 1000 |
return apply_filters('woo_vipps_allow_external_payment_methods', ($finland || $norway || $sweden)); |
| 1001 |
} |
| 1002 |
|
| 1003 |
public function init_form_fields() { |
| 1004 |
global $Vipps; |
| 1005 |
|
| 1006 |
// Used for defaults in the admin interface; however this functions is called a loot more often than that. |
| 1007 |
$page_templates = $this->get_theme_page_templates(); |
| 1008 |
$page_list = $this->get_pagelist(); |
| 1009 |
|
| 1010 |
$orderprefix = $Vipps->generate_order_prefix(); |
| 1011 |
|
| 1012 |
// Default handling based on other parameters and earlier values. |
| 1013 |
$expresscreateuserdefault = "no"; |
| 1014 |
$vippscreateuserdefault = "no"; |
| 1015 |
|
| 1016 |
// Express checkout uses verified email addresses,so we'll create users if the Login plugin is installed and WooCommerce is set to allow user registration. |
| 1017 |
if (class_exists('VippsWooLogin')) { |
| 1018 |
$woodefault = 'yes' === get_option('woocommerce_enable_signup_and_login_from_checkout'); |
| 1019 |
if ($woodefault) { |
| 1020 |
$expresscreateuserdefault = "yes"; |
| 1021 |
// $vippscreateuserdefault = "yes"; // However, for Vipps Checkout the email address is freetext so we'll treat the default a bit different. |
| 1022 |
} |
| 1023 |
} |
| 1024 |
|
| 1025 |
// 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 |
| 1026 |
$vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 1027 |
|
| 1028 |
|
| 1029 |
// 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. |
| 1030 |
$current = get_option('woocommerce_vipps_settings'); |
| 1031 |
// New defaults based on old defaults |
| 1032 |
$default_static_shipping_for_checkout = 'no'; |
| 1033 |
$default_ask_address_for_express = 'no'; |
| 1034 |
$default_status_on_fail = 'failed'; |
| 1035 |
if ($current) { |
| 1036 |
$default_static_shipping_for_checkout = (isset($current['enablestaticshipping'])) ? $current['enablestaticshipping'] : 'no'; |
| 1037 |
$default_ask_address_for_express = (isset($current['useExplicitCheckoutFlow']) && $current['useExplicitCheckoutFlow'] == "yes") ? "yes" : "no"; |
| 1038 |
// The old default used the same value as for Express Checkout. IOK 2023-07-27 |
| 1039 |
$vippscreateuserdefault = isset($current['expresscreateuser']) ? $current['expresscreateuser'] : $vippscreateuserdefault; |
| 1040 |
|
| 1041 |
// For existing installs: set failed payments order status to cancelled to keep same default behaviour. |
| 1042 |
// New installs will be set to failed instead of cancelled. LP 2026-03-26 |
| 1043 |
$default_status_on_fail = 'cancelled'; |
| 1044 |
} |
| 1045 |
|
| 1046 |
// Get the already-set country code. For existing sites, this will guess the country based on the currency; for new sites, use |
| 1047 |
// the woo base country. IOK 2024-10-17 (previously used the currency here too). |
| 1048 |
$countries = new WC_Countries(); // Can't use WC()->countries here - too early IOK 2024-10-17 |
| 1049 |
$country_code = $current['country'] ?? $countries->get_base_country(); |
| 1050 |
|
| 1051 |
// Same issue as above: We need the default payment method name before it is set to be able to provide defaults IOK 2023-12-01 |
| 1052 |
$payment_method_name = $current['payment_method_name'] ?? $this->detect_default_payment_method_from_country($country_code); |
| 1053 |
|
| 1054 |
$checkoutfields = array( |
| 1055 |
'checkout_options' => array( |
| 1056 |
'title' => "Checkout", // Vipps::CheckoutName(), // Don't translate this, but save some space IOK 2024-12-06 |
| 1057 |
'type' => 'title', |
| 1058 |
'class' => 'tab', |
| 1059 |
'description' => sprintf(__("%1\$s is a new service from %2\$s which replaces the usual WooCommerce checkout page entirely, replacing it with a simplified checkout screen providing payment both with %2\$s and credit card. Additionally, your customers will get the option of providing their address information using their %2\$s app directly.", 'woo-vipps'), Vipps::CheckoutName(), Vipps::CompanyName()), |
| 1060 |
), |
| 1061 |
|
| 1062 |
|
| 1063 |
'vipps_checkout_enabled' => array( |
| 1064 |
'title' => sprintf(__('Activate Alternative %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1065 |
'label' => sprintf(__('Enable Alternative %1$s screen, replacing the standard checkout page', 'woo-vipps'), Vipps::CheckoutName()), |
| 1066 |
'type' => 'checkbox', |
| 1067 |
'description' => sprintf(__('If activated, this will <strong>replace</strong> the standard Woo checkout screen with %1$s, providing easy checkout using %1$s or credit card, with no need to type in addresses.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1068 |
'default' => 'no', |
| 1069 |
), |
| 1070 |
|
| 1071 |
'checkoutcreateuser' => array ( |
| 1072 |
'title' => sprintf(__('Create new customers on %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1073 |
'label' => sprintf(__('Create new customers on %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1074 |
'type' => 'checkbox', |
| 1075 |
'description' => sprintf(__('Enable this to create and login customers when using %1$s. Otherwise these will all be guest checkouts. If using, you may want to install Login with Vipps too.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1076 |
'default' => $vippscreateuserdefault, |
| 1077 |
), |
| 1078 |
|
| 1079 |
'enablestaticshipping_checkout' => array( |
| 1080 |
'title' => sprintf(__('Enable static shipping for %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1081 |
'label' => __('Enable static shipping', 'woo-vipps'), |
| 1082 |
'type' => 'checkbox', |
| 1083 |
'description' => sprintf(__('If your shipping options do not depend on the customers address, you can enable \'Static shipping\', which will precompute the shipping options when using %1$s so that this will be much faster. If you do this and the customer isn\'t logged in, the base location of the store will be used to compute the shipping options for the order. You should only use this if your shipping is actually \'static\', that is, does not vary based on the customers address. So fixed price/free shipping will work. If the customer is logged in, their address as registered in the store will be used, so if your customers are always logged in, you may be able to use this too.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1084 |
'default' => $default_static_shipping_for_checkout |
| 1085 |
), |
| 1086 |
|
| 1087 |
|
| 1088 |
'requireUserInfo_checkout' => array( |
| 1089 |
'title' => __('Ask the user to consent to share user information', 'woo-vipps'), |
| 1090 |
'label' => __('Ask the user to consent to share user information', 'woo-vipps'), |
| 1091 |
'type' => 'checkbox', |
| 1092 |
'description' => sprintf(__('If using %1$s, ask for the users consent to share user information with the store. This will allow better integration between Login With %1$s but will add another step to first-time buyers.', 'woo-vipps'), Vipps::CompanyName()), |
| 1093 |
'default' => 'no' |
| 1094 |
), |
| 1095 |
|
| 1096 |
'noAddressFields' => array( |
| 1097 |
'title' => __('Drop the address fields on the Checkout screen', 'woo-vipps'), |
| 1098 |
'label' => __('Don\'t require the address fields', 'woo-vipps'), |
| 1099 |
'type' => 'checkbox', |
| 1100 |
'description' => __('If your products <i>don\'t require shipping</i>, either because they are digital downloads, immaterial products or delivering the products directly on purchase, you can check this box. The user will then not be required to provide an address, which should speed things up a bit. If your products require shipping, this will have no effect. NB: If you have plugins that require shipping information, then this is not going to work very well.','woo-vipps'), |
| 1101 |
'default' => 'no' |
| 1102 |
), |
| 1103 |
|
| 1104 |
'noContactFields' => array( |
| 1105 |
'title' => __('Drop the contact fields on the Checkout screen', 'woo-vipps'), |
| 1106 |
'label' => __('Don\'t require the contact fields', 'woo-vipps'), |
| 1107 |
'type' => 'checkbox', |
| 1108 |
'description' => __('If your products <i>don\'t require shipping</i> as above, and you also don\'t care about the customers name or contact information, you can drop this too! The customer fields will then be filled with a placeholder. NB: If you have plugins that require contact information, then this is not going to work very well. Also, for this to work you have to check the \'no addresses\' box as well.','woo-vipps'), |
| 1109 |
'default' => 'no' |
| 1110 |
), |
| 1111 |
|
| 1112 |
|
| 1113 |
); |
| 1114 |
|
| 1115 |
$vipps_checkout_shipping_fields = array( |
| 1116 |
|
| 1117 |
'checkout_shipping' => array( |
| 1118 |
'title' => sprintf(__('%1$s Shipping Methods', 'woo-vipps'), Vipps::CheckoutName()), |
| 1119 |
'type' => 'title', |
| 1120 |
'description' => sprintf(__("When using %1\$s, you have the option to use %1\$s specific shipping methods with extended features for certain carriers. These will add an apropriate logo as well as extended delivery options for certain methods. For some of these, you need to add integration data from the carriers below. You can then add these shipping methods to your shipping zones the normal way, but they will only appear in the %1\$s screen.", 'woo-vipps'), Vipps::CheckoutName()) |
| 1121 |
), |
| 1122 |
|
| 1123 |
'vcs_posten' => array( |
| 1124 |
'title' => __('Posten Norge', 'woo-vipps'), |
| 1125 |
'class' => 'vcs_posten vcs_main', |
| 1126 |
'custom_attributes' => array('data-vcs-show'=>'.vcs_depend.vcs_posten'), |
| 1127 |
'label' => sprintf(__('Support Posten Norge as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1128 |
'type' => 'checkbox', |
| 1129 |
'description' => sprintf(__('Activate this for Posten Norge as a %1$s Shipping method.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1130 |
'default' => 'yes' |
| 1131 |
), |
| 1132 |
|
| 1133 |
'vcs_posti' => array( |
| 1134 |
'title' => __('Posti', 'woo-vipps'), |
| 1135 |
'class' => 'vcs_posti vcs_main', |
| 1136 |
'custom_attributes' => array('data-vcs-show'=>'.vcs_depend.vcs_posti'), |
| 1137 |
'label' => sprintf(__('Support Posti as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1138 |
'type' => 'checkbox', |
| 1139 |
'description' => sprintf(__('Activate this for Posti as a %1$s Shipping method.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1140 |
'default' => 'yes' |
| 1141 |
), |
| 1142 |
|
| 1143 |
'vcs_postnord' => array( |
| 1144 |
'title' => __('PostNord', 'woo-vipps'), |
| 1145 |
'class' => 'vcs_postnord vcs_main', |
| 1146 |
'custom_attributes' => array('data-vcs-show'=>'.vcs_depend.vcs_postnord'), |
| 1147 |
'label' => sprintf(__('Support PostenNord as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1148 |
'type' => 'checkbox', |
| 1149 |
'description' => sprintf(__('Activate this for PostNord as a %1$s Shipping method.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1150 |
'default' => 'yes' |
| 1151 |
), |
| 1152 |
|
| 1153 |
'vcs_porterbuddy' => array( |
| 1154 |
'title' => __('Porterbuddy', 'woo-vipps'), |
| 1155 |
'class' => 'vcs_porterbuddy vcs_main', |
| 1156 |
'custom_attributes' => array('data-vcs-show'=>'.vcs_depend.vcs_porterbuddy'), |
| 1157 |
'label' => sprintf(__('Support Porterbuddy as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1158 |
'type' => 'checkbox', |
| 1159 |
'description' => sprintf(__('Activate this for Porterbuddy as a %1$s Shipping method. Your store address will be used as the pick-up point and your admin email will be used for booking information from Porterbuddy.' ,'woo-vipps'), Vipps::CheckoutName()), |
| 1160 |
'default' => 'no' |
| 1161 |
), |
| 1162 |
|
| 1163 |
'vcs_porterbuddy_publicToken' => array( |
| 1164 |
'title' => __('Porterbuddy public token', 'woo-vipps'), |
| 1165 |
'class' => 'vippspw vcs_porterbuddy vcs_depend', |
| 1166 |
'type' => 'password', |
| 1167 |
'description' => __('The public key provided to you by Porterbuddy','woo-vipps'), |
| 1168 |
'default' => '', |
| 1169 |
), |
| 1170 |
'vcs_porterbuddy_apiKey' => array( |
| 1171 |
'title' => __('Porterbuddy API key', 'woo-vipps'), |
| 1172 |
'class' => 'vippspw vcs_porterbuddy vcs_depend', |
| 1173 |
'type' => 'password', |
| 1174 |
'description' => __('The API key provided to you by Porterbuddy','woo-vipps'), |
| 1175 |
'default' => '', |
| 1176 |
), |
| 1177 |
'vcs_porterbuddy_phoneNumber' => array( |
| 1178 |
'title' => __('Porterbuddy Phone Number', 'woo-vipps'), |
| 1179 |
'class' => 'vcs_porterbuddy vcs_depend', |
| 1180 |
'type' => 'text', |
| 1181 |
'description' => __('Your phone number where Porterbuddy may send you important messages. Format must be MSISDN (including country code). Example: "4791234567"','woo-vipps'), |
| 1182 |
'default' => '', |
| 1183 |
), |
| 1184 |
|
| 1185 |
// Vipps checkout *shipping options* - extra shipping options that only work with Vipps Checkout |
| 1186 |
'vcs_helthjem' => array( |
| 1187 |
'title' => __('Helthjem', 'woo-vipps'), |
| 1188 |
'label' => sprintf(__('Support Helthjem as a shipping method in %1$s', 'woo-vipps'), Vipps::CheckoutName()), |
| 1189 |
'type' => 'checkbox', |
| 1190 |
'class' => 'vcs_helthjem vcs_main', |
| 1191 |
'custom_attributes' => array('data-vcs-show'=>'.vcs_depend.vcs_helthjem'), |
| 1192 |
'description' => sprintf(__('Activate this for Helthjem as a %1$s Shipping method.' ,'woo-vipps'), Vipps::CheckoutName()), |
| 1193 |
'default' => 'no' |
| 1194 |
), |
| 1195 |
|
| 1196 |
'vcs_helthjem_shopId' => array( |
| 1197 |
'title' => __('Helthjem Shop Id', 'woo-vipps'), |
| 1198 |
'class' => 'vcs_helthjem vcs_depend', |
| 1199 |
'type' => 'number', |
| 1200 |
'custom_attributes' => array('pattern'=>'[0-9]'), |
| 1201 |
'description' => __('The ShopId provided to you by Helthjem','woo-vipps'), |
| 1202 |
'default' => '', |
| 1203 |
), |
| 1204 |
|
| 1205 |
'vcs_helthjem_username' => array( |
| 1206 |
'title' => __('Helthjem Username', 'woo-vipps'), |
| 1207 |
'class' => 'vcs_helthjem vcs_depend', |
| 1208 |
'type' => 'text', |
| 1209 |
'description' => __('The Username provided to you by Helthjem','woo-vipps'), |
| 1210 |
'default' => '', |
| 1211 |
), |
| 1212 |
'vcs_helthjem_password' => array( |
| 1213 |
'title' => __('Helthjem Password', 'woo-vipps'), |
| 1214 |
'class' => 'vippspw vcs_helthjem vcs_depend', |
| 1215 |
'type' => 'password', |
| 1216 |
'description' => __('Password provided to you by Helthjem','woo-vipps'), |
| 1217 |
'default' => '', |
| 1218 |
), |
| 1219 |
|
| 1220 |
); |
| 1221 |
|
| 1222 |
/* Support for *certain* external payment methods in Vipps Checkout. IOK 2024-05-27 */ |
| 1223 |
$externals = []; |
| 1224 |
$external_payment_fields = []; |
| 1225 |
$allow_external_payments = $this->allow_external_payments_in_checkout(); |
| 1226 |
if ($allow_external_payments) { |
| 1227 |
if (in_array('KCO_Gateway', Vipps::$installed_gateways) || in_array('WC_Gateway_Klarna_Payments', Vipps::$installed_gateways)) { |
| 1228 |
$externals['checkout_external_payments_klarna'] = array( |
| 1229 |
'title' => __('Klarna', 'woo-vipps'), |
| 1230 |
'label' => __('Klarna', 'woo-vipps'), |
| 1231 |
'type' => 'checkbox', |
| 1232 |
'class' => 'external_payments klarna', |
| 1233 |
'description' => sprintf(__("Allow Klarna as an external payment method in %1\$s",'woo-vipps'), Vipps::CheckoutName()), |
| 1234 |
'default' => 'no', |
| 1235 |
); |
| 1236 |
} |
| 1237 |
if (!empty($externals)) { |
| 1238 |
$external_payment_fields = [ |
| 1239 |
'checkout_external_payment_title' => array( |
| 1240 |
'title' => sprintf(__('External Payment Methods', 'woo-vipps'), Vipps::CheckoutName()), |
| 1241 |
'type' => 'title', |
| 1242 |
'description' => sprintf(__("Allow certain external payment methods in %1\$s, returning control to WooCommerce for the order", 'woo-vipps'), Vipps::CheckoutName()) |
| 1243 |
), |
| 1244 |
]; |
| 1245 |
foreach($externals as $k => $def) $external_payment_fields[$k] = $def; |
| 1246 |
} |
| 1247 |
} |
| 1248 |
|
| 1249 |
$vipps_checkout_widgets_fields = [ |
| 1250 |
'checkout_widgets' => [ |
| 1251 |
'title' => sprintf(__('%1$s widgets', 'woo-vipps'), Vipps::CheckoutName()), |
| 1252 |
'type' => 'title', |
| 1253 |
'description' => sprintf(__('Widgets are elements shown above the %1$s frame with extra functionality.', 'woo-vipps'), Vipps::CheckoutName()), |
| 1254 |
], |
| 1255 |
'checkout_widget_ordernotes' => [ |
| 1256 |
'title' => __('Order notes', 'woo-vipps'), |
| 1257 |
'label' => __('Enable the order notes widget', 'woo-vipps'), |
| 1258 |
'type' => 'checkbox', |
| 1259 |
'description' => __('A widget to add customer notes with their order.', 'woo-vipps'), |
| 1260 |
'default' => 'yes' |
| 1261 |
], |
| 1262 |
]; |
| 1263 |
/* This seems to have broken some sites with OOM during plugin activation. 2025-05-27 */ |
| 1264 |
// if (wc_coupons_enabled()) { |
| 1265 |
$vipps_checkout_widgets_fields['checkout_widget_coupon'] = [ |
| 1266 |
'title' => __('Coupon code', 'woo-vipps'), |
| 1267 |
'label' => __('Enable the coupon code widget', 'woo-vipps'), |
| 1268 |
'type' => 'checkbox', |
| 1269 |
'description' => __('A widget to activate coupon codes.', 'woo-vipps'), |
| 1270 |
'default' => 'yes' |
| 1271 |
]; |
| 1272 |
// } |
| 1273 |
|
| 1274 |
$mainfields = array( |
| 1275 |
'main_options' => array( |
| 1276 |
'title' => __('Main options', 'woo-vipps'), |
| 1277 |
'type' => 'title', |
| 1278 |
'class' => 'tab', |
| 1279 |
), |
| 1280 |
'enabled' => array( |
| 1281 |
'title' => __('Enable/Disable', 'woocommerce'), |
| 1282 |
'label' => sprintf(__('Enable %1$s', 'woo-vipps'), Vipps::CompanyName()), |
| 1283 |
'type' => 'checkbox', |
| 1284 |
'description' => '', |
| 1285 |
'default' => 'no', |
| 1286 |
), |
| 1287 |
'country' => array( |
| 1288 |
'title' => __('Country', 'woo-vipps'), |
| 1289 |
'label' => __('Country', 'woo-vipps'), |
| 1290 |
'type' => 'select', |
| 1291 |
'options' => array( |
| 1292 |
'NO' => __('Norway', 'woo-vipps'), |
| 1293 |
'SE' => __('Sweden', 'woo-vipps'), |
| 1294 |
'FI' => __('Finland', 'woo-vipps'), |
| 1295 |
'DK' => __('Denmark', 'woo-vipps'), |
| 1296 |
), |
| 1297 |
'description' => __('Select the country for this merchant serial number. This will determine the appropriate payment method (Vipps or MobilePay).', 'woo-vipps'), |
| 1298 |
'default' => $country_code, |
| 1299 |
), |
| 1300 |
|
| 1301 |
'payment_method_name' => array( |
| 1302 |
'title' => __('Payment method', 'woo-vipps'), |
| 1303 |
'label' => __('Choose which payment method should be displayed to users at checkout', 'woo-vipps'), |
| 1304 |
'type' => 'select', |
| 1305 |
'options' => array( |
| 1306 |
'Vipps' => __('Vipps','woo-vipps'), |
| 1307 |
'MobilePay' => __('MobilePay', 'woo-vipps'), |
| 1308 |
), |
| 1309 |
'description' => __('Choose which payment method should be displayed to users at checkout', 'woo-vipps'), |
| 1310 |
'default' => $payment_method_name, |
| 1311 |
), |
| 1312 |
|
| 1313 |
'orderprefix' => array( |
| 1314 |
'title' => __('Order-id Prefix', 'woo-vipps'), |
| 1315 |
'label' => __('Order-id Prefix', 'woo-vipps'), |
| 1316 |
'type' => 'string', |
| 1317 |
'description' => __('An alphanumeric textstring to use as a prefix on orders from your shop, to avoid duplicate order-ids','woo-vipps'), |
| 1318 |
'default' => $orderprefix |
| 1319 |
), |
| 1320 |
'merchantSerialNumber' => array( |
| 1321 |
'title' => __('Merchant Serial Number', 'woo-vipps'), |
| 1322 |
'label' => __('Merchant Serial Number', 'woo-vipps'), |
| 1323 |
'type' => 'number', |
| 1324 |
'description' => __('Your "Merchant Serial Number" from the Developer tab on https://portal.vippsmobilepay.com','woo-vipps'), |
| 1325 |
'default' => '', |
| 1326 |
), |
| 1327 |
'clientId' => array( |
| 1328 |
'title' => __('Client Id', 'woo-vipps'), |
| 1329 |
'class' => 'vippspw', |
| 1330 |
'label' => __('Client Id', 'woo-vipps'), |
| 1331 |
'type' => 'password', |
| 1332 |
'description' => __('Find your account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "Show keys". Copy the value of "client_id"','woo-vipps'), |
| 1333 |
'default' => '', |
| 1334 |
), |
| 1335 |
'secret' => array( |
| 1336 |
'title' => __('Client Secret', 'woo-vipps'), |
| 1337 |
'label' => __('Client Secret', 'woo-vipps'), |
| 1338 |
'class' => 'vippspw', |
| 1339 |
'type' => 'password', |
| 1340 |
'description' => __('Find your account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "show keys". Copy the value of "client_secret"','woo-vipps'), |
| 1341 |
'default' => '', |
| 1342 |
), |
| 1343 |
'Ocp_Apim_Key_eCommerce' => array( |
| 1344 |
'title' => __('Subscription Key', 'woo-vipps'), |
| 1345 |
'label' => __('Subscription Key', 'woo-vipps'), |
| 1346 |
'class' => 'vippspw', |
| 1347 |
'type' => 'password', |
| 1348 |
'description' => __('Find your account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "show keys". Copy the value of "Vipps-Subscription-Key"','woo-vipps'), |
| 1349 |
'default' => '', |
| 1350 |
), |
| 1351 |
|
| 1352 |
'result_status' => array( |
| 1353 |
'title' => sprintf(__('Order status on return from %1$s', 'woo-vipps'), Vipps::CompanyName()), |
| 1354 |
'label' => __('Choose default order status for reserved (not captured) orders', 'woo-vipps'), |
| 1355 |
'type' => 'select', |
| 1356 |
'options' => array( |
| 1357 |
'processing' => __('Processing', 'woo-vipps'), |
| 1358 |
'on-hold' => __('On hold','woo-vipps'), |
| 1359 |
), |
| 1360 |
'description' => __('By default, orders that are <b>reserved</b> but not <b>yet captured</b> will now have the order status \'Processing\'. You can capture the sum manually, or by changing the status to \'Complete\'. You should ensure that your workflow is such that the order is not shipped until after this capture.<br><br> The status \'On hold\' can be chosen instead for stores using a workflow where orders are shipped when the status is \'Processing\'. In this case, \'On hold\' will mean "order is reserved but not yet captured". This is a slightly safer solution, and ensures that the order status will reflect the payment status. <br><br>However, in many stores \'On hold\' has the additional meaning "there is a problem with the order"; and an email is often sent to the customer about this problem. The default is \'Processing\' because of this, and because many plugins and integrations expect orders to be \'Processing\' when the customer has completed payment.', 'woo-vipps'), |
| 1361 |
'default' => 'processing', |
| 1362 |
), |
| 1363 |
|
| 1364 |
'status_on_fail' => array( |
| 1365 |
'title' => sprintf(__('Order status on failed payment (for restartable orders)', 'woo-vipps'), Vipps::CompanyName()), |
| 1366 |
'label' => __('Choose default order status for failed payments', 'woo-vipps'), |
| 1367 |
'type' => 'select', |
| 1368 |
'options' => array( |
| 1369 |
/* translators: woocommerce status name */ |
| 1370 |
'failed' => __('Failed', 'woo-vipps'), |
| 1371 |
/* translators: woocommerce status name */ |
| 1372 |
'cancelled' => __('Cancelled','woo-vipps'), |
| 1373 |
), |
| 1374 |
/* translators: company name. cancelled and failed are woocommerce status names! */ |
| 1375 |
'description' => sprintf(__('By default, orders where payment is started but not completed at %1$s will be set to failed if they can be restarted. This setting changes this behaviour, but does <strong>not</strong> affect orders that cannot be restarted as these will always be set to cancelled.<br><br>Cancelled orders will keep the customer\'s shopping cart intact.<br>Failed orders can be restarted, possibly with another payment method.', 'woo-vipps'), Vipps::CompanyName()), |
| 1376 |
'default' => $default_status_on_fail, |
| 1377 |
), |
| 1378 |
|
| 1379 |
/* |
| 1380 |
'title' => array( |
| 1381 |
'title' => __('Title', 'woocommerce'), |
| 1382 |
'type' => 'text', |
| 1383 |
'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'), |
| 1384 |
'default' => sprintf(__('%1$s','woo-vipps'), $payment_method_name), |
| 1385 |
), |
| 1386 |
*/ |
| 1387 |
|
| 1388 |
'description' => array( |
| 1389 |
'title' => __('Description', 'woocommerce'), |
| 1390 |
'type' => 'textarea', |
| 1391 |
'description' => __('This controls the description which the user sees during checkout.', 'woocommerce'), |
| 1392 |
'default' => __("Pay safely and easily. No fees, no matter the amount.", 'woo-vipps'), |
| 1393 |
), |
| 1394 |
|
| 1395 |
'vippsdefault' => array( |
| 1396 |
'title' => sprintf(__('Use %1$s as default payment method on checkout page', 'woo-vipps'), $payment_method_name), |
| 1397 |
'label' => sprintf(__('%1$s is default payment method', 'woo-vipps'), $payment_method_name), |
| 1398 |
'type' => 'checkbox', |
| 1399 |
'description' => sprintf(__('Enable this to use %1$s as the default payment method on the checkout page, regardless of order.', 'woo-vipps'), $payment_method_name), |
| 1400 |
'default' => 'yes', |
| 1401 |
), |
| 1402 |
|
| 1403 |
'checkout_phone_transformation' => array( |
| 1404 |
'title' => sprintf(__('Phone number transformation for %1$s and Express Checkout', 'woo-vipps'), 'Checkout'), |
| 1405 |
'label' => sprintf(__('Choose a transformation to apply to phone numbers for %1$s and Express Checkout', 'woo-vipps'), 'Checkout'), |
| 1406 |
'type' => 'select', |
| 1407 |
'options' => array( |
| 1408 |
'none' => __('None', 'woo-vipps'), |
| 1409 |
'ensure_plus' => __('Prepend \'+\'','woo-vipps'), |
| 1410 |
'strip_country_code' => __('Strip country code','woo-vipps'), |
| 1411 |
), |
| 1412 |
'description' => __('Phone numbers from Express or Checkout are in the format 47xxxxxx without plus-sign in front. If you prefer, or if it is neccessary for your integrations, you can transform these numbers either by adding the plus sign or by stripping the country-code (45, 46, 47, 358).<br>NB: stripping country codes is at the moment only supported for Norwegian, Danish, Finnish and Swedish numbers.<br>Remember to explicitly test your use case before committing to any transformation on your live store.', 'woo-vipps'), |
| 1413 |
'default' => 'none', |
| 1414 |
), |
| 1415 |
); |
| 1416 |
|
| 1417 |
$expressfields = array( |
| 1418 |
'express_options' => array( |
| 1419 |
'title' => sprintf(__('Express Checkout', 'woo-vipps')), |
| 1420 |
'type' => 'title', |
| 1421 |
'class' => 'tab', |
| 1422 |
'description' => sprintf(__("%1\$s allows you to buy products by a single click from the cart page or directly from product or catalog pages. Product will get a 'buy now' button which will start the purchase process immediately.", 'woo-vipps'), Vipps::ExpressCheckoutName()) |
| 1423 |
), |
| 1424 |
|
| 1425 |
'cartexpress' => array( |
| 1426 |
'title' => __('Enable Express Checkout in cart', 'woo-vipps'), |
| 1427 |
'label' => __('Enable Express Checkout in cart', 'woo-vipps'), |
| 1428 |
'type' => 'checkbox', |
| 1429 |
'description' => sprintf(__('Enable this to allow customers to shop using %1$s directly from the cart with no login or address input needed', 'woo-vipps'), Vipps::ExpressCheckoutName()) . '.<br>' . |
| 1430 |
sprintf(__('Please note that for Express Checkout, shipping must be calculated in a callback from the %1$s app, without any knowledge of the customer. This means that Express Checkout may not be compatible with all Shipping plugins or setup. You should test that your setup works if you intend to provide this feature.', 'woo-vipps'), Vipps::CompanyName()), |
| 1431 |
'default' => 'yes', |
| 1432 |
), |
| 1433 |
|
| 1434 |
'singleproductexpress' => array( |
| 1435 |
'title' => __('Enable Express Checkout for single products', 'woo-vipps'), |
| 1436 |
'label' => __('Enable Express Checkout for single products', 'woo-vipps'), |
| 1437 |
'type' => 'select', |
| 1438 |
'options' => array( |
| 1439 |
'none' => __('No products','woo-vipps'), |
| 1440 |
'some' => __('Some products', 'woo-vipps'), |
| 1441 |
'all' => __('All products','woo-vipps') |
| 1442 |
), |
| 1443 |
'description' => sprintf(__('Enable this to allow customers to buy a product using %1$s directly from the product page. If you choose \'some\', you must enable this on the relevant products', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 1444 |
'default' => 'none', |
| 1445 |
), |
| 1446 |
'singleproductexpressarchives' => array( |
| 1447 |
'title' => __('Add \'Buy now\' button on catalog pages too', 'woo-vipps'), |
| 1448 |
'label' => __('Add the button for all relevant products on catalog pages', 'woo-vipps'), |
| 1449 |
'type' => 'checkbox', |
| 1450 |
'description' => sprintf(__('If %1$s is enabled for a product, add the \'Buy now\' button to catalog pages too', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 1451 |
'default' => 'no', |
| 1452 |
), |
| 1453 |
'expresscheckout_termscheckbox' => array( |
| 1454 |
'title' => sprintf(__('Add terms and conditions checkbox on %1$s', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 1455 |
'label' => sprintf(__('Always ask for confirmation on %1$s', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 1456 |
'type' => 'checkbox', |
| 1457 |
'description' => sprintf(__('When using %1$s, ask the user to confirm that they have read and accepted the stores terms and conditons before proceeding', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 1458 |
'default' => 'no', |
| 1459 |
), |
| 1460 |
|
| 1461 |
'expresscheckout_always_address' => array( |
| 1462 |
'title' => __('Always ask for address, even if products don\'t need shipping', 'woo-vipps'), |
| 1463 |
'label' => __('Always ask the user for their address, even if you don\'t need it for shipping', 'woo-vipps'), |
| 1464 |
'type' => 'checkbox', |
| 1465 |
'description' => __('If the order contains only "virtual" products that do not need shipping, we do not normally ask the user for their address - but check this box to do so anyway.', 'woo-vipps'), |
| 1466 |
'default' => $default_ask_address_for_express, |
| 1467 |
), |
| 1468 |
|
| 1469 |
'enablestaticshipping' => array( |
| 1470 |
'title' => __('Enable static shipping for Express Checkout', 'woo-vipps'), |
| 1471 |
'label' => __('Enable static shipping', 'woo-vipps'), |
| 1472 |
'type' => 'checkbox', |
| 1473 |
'description' => __('If your shipping options do not depend on the customers address, you can enable \'Static shipping\', which will precompute the shipping options when using Express Checkout so that this will be much faster. If you do this and the customer isn\'t logged in, the base location of the store will be used to compute the shipping options for the order. You should only use this if your shipping is actually \'static\', that is, does not vary based on the customers address. So fixed price/free shipping will work. If the customer is logged in, their address as registered in the store will be used, so if your customers are always logged in, you may be able to use this too.', 'woo-vipps'), |
| 1474 |
'default' => 'no', |
| 1475 |
), |
| 1476 |
|
| 1477 |
|
| 1478 |
'expresscreateuser' => array ( |
| 1479 |
'title' => __('Create new customers on Express Checkout', 'woo-vipps'), |
| 1480 |
'label' => __('Create new customers on Express Checkout', 'woo-vipps'), |
| 1481 |
'type' => 'checkbox', |
| 1482 |
'description' => sprintf(__('Enable this to create and login new customers when using express checkout. Otherwise these will all be guest checkouts. If you have "Login with Vipps" installed, this will be the default (unless you have turned off user creation in WooCommerce itself)', 'woo-vipps'), Vipps::CompanyName()), |
| 1483 |
'default' => $expresscreateuserdefault, |
| 1484 |
), |
| 1485 |
'singleproductbuynowcompatmode' => array( |
| 1486 |
'title' => __('"Buy now" compatibility mode', 'woo-vipps'), |
| 1487 |
'label' => __('Activate compatibility mode for all "Buy now" buttons', 'woo-vipps'), |
| 1488 |
'type' => 'checkbox', |
| 1489 |
'description' => __('Choosing this will use a different method of handling the "Buy now" button on a single product, which will work for more product types and more plugins - while being <i>slightly</i> less smooth. Use this if your product needs more configuration than simple or standard variable products', 'woo-vipps'), |
| 1490 |
'default' => 'no', |
| 1491 |
), |
| 1492 |
|
| 1493 |
|
| 1494 |
'deletefailedexpressorders' => array( |
| 1495 |
'title' => __('Delete failed Express Checkout Orders', 'woo-vipps'), |
| 1496 |
'label' => __('Delete failed Express Checkout Orders', 'woo-vipps'), |
| 1497 |
'type' => 'checkbox', |
| 1498 |
'description' => __('As Express Checkout orders are anonymous, failed orders will end up as "cancelled" orders with no information in them. Enable this to delete these automatically when cancelled - but test to make sure no other plugin needs them for anything.', 'woo-vipps'), |
| 1499 |
'default' => 'no', |
| 1500 |
) |
| 1501 |
); |
| 1502 |
// New shipping in express checkout is available, but merchant has overridden the old shipping callback. Ask what to do! IOK 2020-02-12 |
| 1503 |
if (has_action('woo_vipps_shipping_methods')) { |
| 1504 |
$shippingoptions = array( |
| 1505 |
'newshippingcallback' => array( |
| 1506 |
'title' => __('Use old-style shipping callback for express checkout', 'woo-vipps'), |
| 1507 |
'label' => __('Use your current shipping filters', 'woo-vipps'), |
| 1508 |
'type' => 'select', |
| 1509 |
'options' => array( |
| 1510 |
'none' => __('Select one','woo-vipps'), |
| 1511 |
'old' => __('Keep using old shipping callback with my custom filter', 'woo-vipps'), |
| 1512 |
'new' => __('Use new shipping callback','woo-vipps') |
| 1513 |
), |
| 1514 |
'description' => sprintf(__('Since version 1.4 this plugin uses a new method of providing shipping methods to %1$s when using Express Checkout. The new method supports metadata in the shipping options, which is neccessary for integration with Bring, Postnord etc. However, the new method is not compatible with the old <code>\'woo_vipps_shipping_methods\'</code> filter, which your site has overridden in a theme or plugin. If you want to, you can continue using this filter and the old method. If you want to disable your filters and use the new method, you can choose this here. ', 'woo-vipps'), Vipps::CompanyName()), |
| 1515 |
'default' => 'none', |
| 1516 |
) |
| 1517 |
); |
| 1518 |
$expressfields = array_merge(array_slice($expressfields ,0,1), $shippingoptions, array_slice($expressfields,1)); |
| 1519 |
} |
| 1520 |
|
| 1521 |
$advancedfields = array( |
| 1522 |
'advanced_options' => array( |
| 1523 |
'title' => __('Advanced', 'woo-vipps'), |
| 1524 |
'type' => 'title', |
| 1525 |
'class' => 'tab', |
| 1526 |
'description' => __("If you have issues with your theme, you might find a setting here that will help. Normally you do not need to change these.", 'woo-vipps') |
| 1527 |
), |
| 1528 |
|
| 1529 |
'vippsorderattribution' => array( |
| 1530 |
'title' => __( 'Support WooCommerces Order Attribution API for Checkout and Express Checkout', 'woo-vipps' ), |
| 1531 |
'label' => __( 'Add support for Order Attribution', 'woo-vipps' ), |
| 1532 |
'type' => 'checkbox', |
| 1533 |
'default'=> 'no', |
| 1534 |
'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'), |
| 1535 |
), |
| 1536 |
|
| 1537 |
'vippsspecialpagetemplate' => array( |
| 1538 |
'title' => sprintf(__('Override page template used for the special %1$s pages', 'woo-vipps'), Vipps::CompanyName()), |
| 1539 |
'label' => sprintf(__('Use specific template for %1$s', 'woo-vipps'), Vipps::CompanyName()), |
| 1540 |
'type' => 'select', |
| 1541 |
'options' => $page_templates, |
| 1542 |
'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()), |
| 1543 |
'default' => ''), |
| 1544 |
|
| 1545 |
'vippsspecialpageid' => array( |
| 1546 |
'title' => sprintf(__('Use a real page ID for the special %1$s pages - neccessary for some themes', 'woo-vipps'), Vipps::CompanyName()), |
| 1547 |
'label' => __('Use a real page ID', 'woo-vipps'), |
| 1548 |
'type' => 'select', |
| 1549 |
'options' => $page_list, |
| 1550 |
'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()), |
| 1551 |
'default'=>''), |
| 1552 |
|
| 1553 |
'sendreceipts' => array( |
| 1554 |
'title' => __("Send receipts and order confirmation info to the customers' app on completed purchases.", 'woo-vipps'), |
| 1555 |
'label' => sprintf(__("Send receipts to the customers %1\$s app", 'woo-vipps'), Vipps::CompanyName()), |
| 1556 |
'type' => 'checkbox', |
| 1557 |
'description' => sprintf(__("If this is checked, a receipt will be sent to %1\$s which will be viewable in the users' app, specifying the order items, shipping et cetera", 'woo-vipps'), Vipps::CompanyName()), |
| 1558 |
'default' => 'yes' |
| 1559 |
), |
| 1560 |
|
| 1561 |
'receiptimage' => array ( |
| 1562 |
'title' => sprintf(__('Use this image for the order confirmation link uploaded to the customers\' %1$s app', 'woo-vipps'), Vipps::CompanyName()), |
| 1563 |
'label' => sprintf(__('Profile image used in the %1$s App', 'woo-vipps'), Vipps::CompanyName()), |
| 1564 |
'type' => 'woo_vipps_image', |
| 1565 |
'description' => sprintf(__('If set, this image will be uploaded to %1$s and used to profile your store in the %1$s app for links to the order confirmation etc', 'woo-vipps'), Vipps::CompanyName()), |
| 1566 |
'default' => 0, |
| 1567 |
), |
| 1568 |
|
| 1569 |
|
| 1570 |
'use_flock' => array ( |
| 1571 |
'title' => __('Use flock() to lock orders for Express Checkout', 'woo-vipps'), |
| 1572 |
'label' => __('Use flock() to lock orders for Express Checkout', 'woo-vipps'), |
| 1573 |
'type' => 'checkbox', |
| 1574 |
'description' => __('Use the flock() system call to ensure orders are only finalized once. You can use this for normal setups, but probably not on Windows with IIS, and possibly not on distributed filesystems like NFS. If you don\t know what it is, probably do not use it. If you get duplicated shipping lines on some express orders, you may try using this', 'woo-vipps'), |
| 1575 |
'default' => 'no', |
| 1576 |
), |
| 1577 |
|
| 1578 |
'delete_settings_on_deactivation' => array ( |
| 1579 |
'title' => __('Delete plugin settings on deactivation', 'woo-vipps'), |
| 1580 |
'label' => __('Delete plugin settings on deactivation', 'woo-vipps'), |
| 1581 |
'type' => 'checkbox', |
| 1582 |
'description' => __('If set, all plugin settings will be deleted upon plugin deactivation. Warning: there is no recovery after deletion.', 'woo-vipps'), |
| 1583 |
'default' => 'no', |
| 1584 |
), |
| 1585 |
|
| 1586 |
'developermode' => array ( // DEVELOPERS! DEVELOPERS! DEVELOPERS! DEVE |
| 1587 |
'title' => __('Enable developer mode', 'woo-vipps'), |
| 1588 |
'label' => __('Enable developer mode', 'woo-vipps'), |
| 1589 |
'type' => 'checkbox', |
| 1590 |
'description' => __('Enable this to enter developer mode. This gives you access to the test-api and sometimes other tools not yet ready for general consumption', 'woo-vipps'), |
| 1591 |
'default' => VIPPS_TEST_MODE ? 'yes' : 'no', |
| 1592 |
) |
| 1593 |
|
| 1594 |
|
| 1595 |
); |
| 1596 |
|
| 1597 |
$developerfields = array( |
| 1598 |
'developertitle' => array( |
| 1599 |
'title' => __('Developer mode', 'woo-vipps'), |
| 1600 |
'type' => 'title', |
| 1601 |
'class' => 'tab', |
| 1602 |
'description' => __('These are settings for developers that contain extra features that are normally not useful for regular users, or are not yet ready for primetime', 'woo-vipps'), |
| 1603 |
), |
| 1604 |
|
| 1605 |
'testmode' => array( |
| 1606 |
'title' => __('Test mode', 'woo-vipps'), |
| 1607 |
'label' => __('Enable test mode', 'woo-vipps'), |
| 1608 |
'type' => 'checkbox', |
| 1609 |
'description' => sprintf(__('If you enable this, transactions will be made towards the %1$s Test API instead of the live one. No real transactions will be performed. You will need to fill out your test |
| 1610 |
accounts keys below, and you will need to install a special test-mode app from Testflight on a device (which cannot run the regular %1$s app). Contact %1$s\'s technical support if you need this. If you turn this mode off, normal operation will resume. If you have the VIPPS_TEST_MODE defined in your wp-config file, this will override this value. ', 'woo-vipps'), Vipps::CompanyName()), |
| 1611 |
'default' => VIPPS_TEST_MODE ? 'yes' : 'no', |
| 1612 |
), |
| 1613 |
'merchantSerialNumber_test' => array( |
| 1614 |
'title' => __('Merchant Serial Number', 'woo-vipps'), |
| 1615 |
'class' => 'vippspw', |
| 1616 |
'label' => __('Merchant Serial Number', 'woo-vipps'), |
| 1617 |
'type' => 'number', |
| 1618 |
'description' => __('Your test account "Merchant Serial Number" from the Developer tab on https://portal.vippsmobilepay.com','woo-vipps'), |
| 1619 |
'default' => '', |
| 1620 |
), |
| 1621 |
'clientId_test' => array( |
| 1622 |
'title' => __('Client Id', 'woo-vipps'), |
| 1623 |
'label' => __('Client Id', 'woo-vipps'), |
| 1624 |
'type' => 'password', |
| 1625 |
'class' => 'vippspw', |
| 1626 |
'description' => __('Find your test account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "Show keys". Copy the value of "client_id"','woo-vipps'), |
| 1627 |
'default' => '', |
| 1628 |
), |
| 1629 |
'secret_test' => array( |
| 1630 |
'title' => __('Client Secret', 'woo-vipps'), |
| 1631 |
'label' => __('Client Secret', 'woo-vipps'), |
| 1632 |
'type' => 'password', |
| 1633 |
'class' => 'vippspw', |
| 1634 |
'description' => __('Find your test account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "show keys". Copy the value of "client_secret"','woo-vipps'), |
| 1635 |
'default' => '', |
| 1636 |
), |
| 1637 |
'Ocp_Apim_Key_eCommerce_test' => array( |
| 1638 |
'title' => __('Subscription Key', 'woo-vipps'), |
| 1639 |
'label' => __('Subscription Key', 'woo-vipps'), |
| 1640 |
'type' => 'password', |
| 1641 |
'class' => 'vippspw', |
| 1642 |
'description' => __('Find your test account under the "Developer" tab on https://portal.vippsmobilepay.com/ and choose "show keys". Copy the value of "Vipps-Subscription-Key"','woo-vipps'), |
| 1643 |
'default' => '', |
| 1644 |
), |
| 1645 |
); |
| 1646 |
|
| 1647 |
// Add all the standard fields |
| 1648 |
foreach($mainfields as $key=>$field) { |
| 1649 |
$this->form_fields[$key] = $field; |
| 1650 |
} |
| 1651 |
foreach($expressfields as $key=>$field) { |
| 1652 |
$this->form_fields[$key] = $field; |
| 1653 |
} |
| 1654 |
foreach($checkoutfields as $key=>$field) { |
| 1655 |
$this->form_fields[$key] = $field; |
| 1656 |
} |
| 1657 |
|
| 1658 |
|
| 1659 |
foreach($external_payment_fields as $key=>$field) { |
| 1660 |
$this->form_fields[$key] = $field; |
| 1661 |
} |
| 1662 |
|
| 1663 |
foreach($vipps_checkout_widgets_fields as $key=>$field) { |
| 1664 |
$this->form_fields[$key] = $field; |
| 1665 |
} |
| 1666 |
|
| 1667 |
foreach($vipps_checkout_shipping_fields as $key=>$field) { |
| 1668 |
$this->form_fields[$key] = $field; |
| 1669 |
} |
| 1670 |
foreach($advancedfields as $key=>$field) { |
| 1671 |
$this->form_fields[$key] = $field; |
| 1672 |
} |
| 1673 |
|
| 1674 |
// The react UI decides whether or not to show the developer fields, however we always have to send this data to the client |
| 1675 |
// because otherwise the react UI will not be able to show the correct translations, since they would be missing. |
| 1676 |
foreach($developerfields as $key=>$field) { |
| 1677 |
$this->form_fields[$key] = $field; |
| 1678 |
} |
| 1679 |
// Developer mode settings: Only shown when active. IOK 2019-08-30 |
| 1680 |
if ($this->get_option('developermode') == 'yes' || VIPPS_TEST_MODE) { |
| 1681 |
if (VIPPS_TEST_MODE) { |
| 1682 |
$this->form_fields['developermode']['description'] .= '<br><b>' . __('VIPPS_TEST_MODE is set to true in your configuration - dev mode is forced', 'woo-vipps') . "</b>"; |
| 1683 |
$this->form_fields['testmode']['description'] .= '<br><b>' . __('VIPPS_TEST_MODE is set to true in your configuration - test mode is forced', 'woo-vipps') . "</b>"; |
| 1684 |
} |
| 1685 |
} |
| 1686 |
|
| 1687 |
|
| 1688 |
} |
| 1689 |
|
| 1690 |
|
| 1691 |
// IOK 2018-04-18 utilities for the 'admin notices' interface. |
| 1692 |
private function adminwarn($what) { |
| 1693 |
add_action('vipps_admin_notices',function() use ($what) { |
| 1694 |
echo "<div class='notice notice-warning is-dismissible'><p>$what</p></div>"; |
| 1695 |
}); |
| 1696 |
} |
| 1697 |
private function adminerr($what) { |
| 1698 |
add_action('vipps_admin_notices',function() use ($what) { |
| 1699 |
echo "<div class='notice notice-error is-dismissible'><p>$what</p></div>"; |
| 1700 |
}); |
| 1701 |
} |
| 1702 |
private function adminnotify($what) { |
| 1703 |
add_action('vipps_admin_notices',function() use ($what) { |
| 1704 |
echo "<div class='notice notice-info is-dismissible'><p>$what</p></div>"; |
| 1705 |
}); |
| 1706 |
} |
| 1707 |
|
| 1708 |
// Only be available if current currency is NOK IOK 2018-09-19 |
| 1709 |
// Only be available if current currency is supported NT 2023-12-04 |
| 1710 |
public function is_available() { |
| 1711 |
// This is split into two functions to avoid triggering infinite recursion in filters that override this value below. IOK 2021-10-30 |
| 1712 |
$ok = $this->standard_is_available(); |
| 1713 |
$ok = apply_filters('woo_vipps_is_available', $ok, $this); |
| 1714 |
return $ok; |
| 1715 |
} |
| 1716 |
|
| 1717 |
// True if the alternative Vipps Checkout screen is both available and activated. Returns the page id of the checkout |
| 1718 |
// page for convenience. IOK 2021-10-01 |
| 1719 |
public function vipps_checkout_available () { |
| 1720 |
|
| 1721 |
if ($this->get_option('vipps_checkout_enabled') != 'yes') return false; |
| 1722 |
if (!$this->standard_is_available()) return false; |
| 1723 |
|
| 1724 |
$checkoutid = wc_get_page_id('vipps_checkout'); |
| 1725 |
if (!$checkoutid) return false; |
| 1726 |
|
| 1727 |
// Page doesn't exist anymore |
| 1728 |
if (! get_post_status($checkoutid)) { |
| 1729 |
delete_option('woocommerce_vipps_checkout_page_id'); |
| 1730 |
return false; |
| 1731 |
} |
| 1732 |
|
| 1733 |
// Restrictions on cart are similar to express checkout, but not exactly the same. IOK 2024-01-11 |
| 1734 |
if (!$this->cart_supports_checkout()) return false; |
| 1735 |
|
| 1736 |
// Filter to false if you want to use the standard checkout for whatever reason |
| 1737 |
return apply_filters('woo_vipps_checkout_available', $checkoutid, $this); |
| 1738 |
} |
| 1739 |
|
| 1740 |
// Get supported currencies for payment method NT 2023-12-04 |
| 1741 |
public function get_supported_currencies($payment_method) { |
| 1742 |
switch ($payment_method) { |
| 1743 |
case 'Vipps': return array('NOK', 'SEK'); |
| 1744 |
case 'MobilePay': return array('DKK', 'EUR'); |
| 1745 |
default: return array(); |
| 1746 |
} |
| 1747 |
} |
| 1748 |
|
| 1749 |
// Check if payment method supports currency. NT 2023-12-04 |
| 1750 |
public function payment_method_supports_currency($payment_method, $currency) { |
| 1751 |
$ok = in_array($currency, $this->get_supported_currencies($payment_method)); |
| 1752 |
return $ok; |
| 1753 |
} |
| 1754 |
|
| 1755 |
// Basic unfiltered version of "can I use vipps" ? IOK 2021-10-01 |
| 1756 |
protected function standard_is_available () { |
| 1757 |
if (!$this->can_be_activated()) return false; |
| 1758 |
if (!parent::is_available()) return false; |
| 1759 |
if (!$this->payment_method_supports_currency($this->get_payment_method_name(), get_woocommerce_currency())) return false; |
| 1760 |
return true; |
| 1761 |
} |
| 1762 |
|
| 1763 |
// True if the express checkout feature should be available |
| 1764 |
public function express_checkout_available() { |
| 1765 |
if (! $this->is_available()) return false; |
| 1766 |
$ok = true; |
| 1767 |
$ok = apply_filters('woo_vipps_express_checkout_available', $ok, $this); |
| 1768 |
return $ok; |
| 1769 |
} |
| 1770 |
|
| 1771 |
// IOK 2018-04-20 Initiate payment at Vipps and redirect to the Vipps payment terminal. |
| 1772 |
public function process_payment ($order_id) { |
| 1773 |
global $woocommerce, $Vipps; |
| 1774 |
if (!$order_id) return []; |
| 1775 |
|
| 1776 |
do_action('woo_vipps_before_process_payment',$order_id); |
| 1777 |
|
| 1778 |
// Get current merchant serial |
| 1779 |
$msn = $this->get_merchant_serial(); |
| 1780 |
|
| 1781 |
// Do a quick check for correct setup first - this is the most critical point IOK 2018-05-11 |
| 1782 |
try { |
| 1783 |
$at = $this->api->get_access_token($msn); |
| 1784 |
} catch (Exception $e) { |
| 1785 |
$this->log(sprintf(__('Could not get access token when initiating %1$s payment for order id:','woo-vipps'), $this->get_payment_method_name()) . $order_id .":\n" . $e->getMessage(), 'error'); |
| 1786 |
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'); |
| 1787 |
return []; |
| 1788 |
} |
| 1789 |
|
| 1790 |
|
| 1791 |
// From the request, get either [billing_phone] => or [vipps phone] |
| 1792 |
$phone = ''; |
| 1793 |
if (isset($_POST['vippsphone'])) { |
| 1794 |
$phone = trim(sanitize_text_field($_POST['vippsphone'])); |
| 1795 |
} |
| 1796 |
if (!$phone && isset($_POST['billing_phone'])) { |
| 1797 |
$phone = trim(sanitize_text_field($_POST['billing_phone'])); |
| 1798 |
} |
| 1799 |
|
| 1800 |
// This is for express checkout if we know the customers' phone. |
| 1801 |
// thanks to sOndre @ github for reporting , https://github.com/vippsas/vipps-woocommerce/issues/22 |
| 1802 |
if (!$phone && WC()->customer) { |
| 1803 |
$phone = WC()->customer->get_billing_phone(); |
| 1804 |
} |
| 1805 |
|
| 1806 |
// No longer the case for V2 of the API |
| 1807 |
if (false && !$phone) { |
| 1808 |
wc_add_notice(sprintf(__('You need to enter your phone number to pay with %1$s','woo-vipps'), $this->get_payment_method_name()) ,'error'); |
| 1809 |
return []; |
| 1810 |
} |
| 1811 |
|
| 1812 |
$order = wc_get_order($order_id); |
| 1813 |
$content = null; |
| 1814 |
|
| 1815 |
// Should be impossible, but there we go IOK 2022-04-21 |
| 1816 |
if (! $order->has_status(['pending', 'failed'])) { |
| 1817 |
$this->log(sprintf(__("Trying to start order %1\$s with status %2\$s - only 'pending' and 'failed' are allowed, so this will fail", 'woo-vipps'), $order_id, $order->get_status())); |
| 1818 |
wc_add_notice(sprintf(__('This order cannot be paid with %1$s - please try another payment method or try again later', 'woo-vipps'), $this->get_payment_method_name()), 'error'); |
| 1819 |
return []; |
| 1820 |
} |
| 1821 |
|
| 1822 |
// If the Vipps order already has an init-timestamp, we should *not* call init_payment again, |
| 1823 |
// in the *normal* case, this is a user who have lost their vipps session, so it suffices to |
| 1824 |
// just return the stored vipps session URL (eg. the user used the Back button.) If abandoned, the |
| 1825 |
// order will eventually be cancelled. Changes in the cart will result in a new order anyway. |
| 1826 |
// Note: we now also support restarting the payment with a new retry session if there is no stored vipps session. LP 2026-03-12 |
| 1827 |
if ($order->get_meta('_vipps_init_timestamp')) { |
| 1828 |
$oldurl = $order->get_meta('_vipps_orderurl'); |
| 1829 |
|
| 1830 |
// Poll status at VMP here to verify session is still open. LP 2026-02-27 |
| 1831 |
$vipps_status = 'unknown'; |
| 1832 |
$vipps_session_open = false; |
| 1833 |
try { |
| 1834 |
$vipps_status = $this->get_payment_details($order)['status'] ?? 'unknown'; |
| 1835 |
if ('unknown' !== $vipps_status) { |
| 1836 |
$vipps_status = $this->interpret_vipps_order_status($vipps_status); |
| 1837 |
$vipps_session_open = 'initiated' === $vipps_status; |
| 1838 |
} |
| 1839 |
} catch (Exception $e) { |
| 1840 |
/* translators: company name, order id */ |
| 1841 |
$this->log(sprintf(__('Retry-branch: error getting payment details from %1$s for order id %2$s:','woo-vipps'), $this->get_payment_method_name(), $order->get_id()) . "\n" . $e->getMessage(), 'error'); |
| 1842 |
} |
| 1843 |
|
| 1844 |
// Do we have an active session we can redirect to? LP 2026-02-27 |
| 1845 |
if ($vipps_session_open && $oldurl) { |
| 1846 |
$order->add_order_note(sprintf(__('%1$s payment restarted','woo-vipps'), $this->get_payment_method_name())); |
| 1847 |
return array('result'=>'success','redirect'=>$oldurl); |
| 1848 |
} |
| 1849 |
|
| 1850 |
// If not, then we need to create a new retry session with incrementing index. LP 2026-03-12 |
| 1851 |
$retry_count = intval($order->get_meta('_vipps_retry_count')) + 1; |
| 1852 |
$enable_payment_retry = 'unknown' !== $vipps_status; // dont retry unknown statuses. LP 2026-03-18 |
| 1853 |
if (apply_filters('woo_vipps_enable_payment_retry', $enable_payment_retry, $order, $vipps_status, $retry_count)) { |
| 1854 |
/* translators: number of attempts, order id. */ |
| 1855 |
$this->log(sprintf(__('Order %2$d session could not be restored, creating a new retry session (retry #%1$d).', 'woo-vipps'), $retry_count, $order_id), 'info'); |
| 1856 |
$order->update_meta_data('_vipps_retry_count', $retry_count); |
| 1857 |
$order->save_meta_data(); |
| 1858 |
} else { |
| 1859 |
// If restarting is disabled, then we have to cancel the order. LP 2026-03-12 |
| 1860 |
/* translators: order id. */ |
| 1861 |
$this->log(sprintf(__('Order %1$d session could not be restored, and payment retry is disabled. Cancelling the order!', 'woo-vipps'), $order_id), 'info'); |
| 1862 |
$order->update_status('cancelled', sprintf(__('Cannot restart order at %1$s', 'woo-vipps'), Vipps::CompanyName())); |
| 1863 |
return []; |
| 1864 |
} |
| 1865 |
} |
| 1866 |
|
| 1867 |
// This is needed to ensure that the callbacks from Vipps have access to the customers' session which is important for some plugins. IOK 2019-11-22 |
| 1868 |
$this->save_session_in_order($order); |
| 1869 |
|
| 1870 |
// Vipps-terminal-page return url to poll/await return |
| 1871 |
$returnurl= $Vipps->payment_return_url(); |
| 1872 |
// If we are using express checkout, use this to handle the address stuff |
| 1873 |
// IOK 2018-11-19 also when *not* using express checkout. This allows us to pass the order-id in the return URL and use this as a password in case the sesson has been lost. |
| 1874 |
$authtoken = $this->generate_authtoken(); |
| 1875 |
|
| 1876 |
// IOK 2019-11-19 We have to do this because even though we actually store the order ID in the session, we can a) be redirected to another browser than the one with |
| 1877 |
// the session, and b) some plugins wipe the session for guest purchases. |
| 1878 |
// So we might need to restore (enough of the) session to get to the than you page, even if the session is gone |
| 1879 |
// or in another castle. |
| 1880 |
// IOK 2023-01-23 store the limited session in the order instead and separeted it from the authtoken |
| 1881 |
$limited_session = $this->generate_authtoken(); |
| 1882 |
$returnurl = add_query_arg('ls',$limited_session,$returnurl); |
| 1883 |
$returnurl = add_query_arg('id', $order_id, $returnurl); |
| 1884 |
|
| 1885 |
|
| 1886 |
try { |
| 1887 |
// If the order was 'failed', it isnt any more! yet! |
| 1888 |
if ($order->get_status() == 'failed') { |
| 1889 |
$order->set_status('pending', __('Setting order status to pending to start payment', 'woo-vipps')); |
| 1890 |
} |
| 1891 |
// The requestid is actually for replaying the request, but I get 402 if I retry with the same Orderid. |
| 1892 |
// Still, if we want to handle transient error conditions, then that needs to be extended here (timeouts, etc) |
| 1893 |
// Update: replaced requestid with new idempotency key, moved this into epayment_initiate_payment. LP 2026-03-13 |
| 1894 |
$content = $this->api->epayment_initiate_payment($phone,$order,$returnurl,$authtoken); |
| 1895 |
} catch (TemporaryVippsApiException $e) { |
| 1896 |
$this->log(sprintf(__('Could not initiate %1$s payment','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage(), 'error'); |
| 1897 |
wc_add_notice(sprintf(__('Unfortunately, the %1$s payment method is temporarily unavailable. Please wait or choose another method.','woo-vipps'), $this->get_payment_method_name()),'error'); |
| 1898 |
return []; |
| 1899 |
} catch (Exception $e) { |
| 1900 |
|
| 1901 |
// Special case the "duplicate order id" thing to ensure it doesn't happen again, and if it does, at least |
| 1902 |
// log some more info IOK 2022-11-02 |
| 1903 |
if (preg_match("/Duplicate Order Id/i", $e->getMessage())) { |
| 1904 |
do_action('woo_vipps_duplicate_order_id', $order); |
| 1905 |
$this->log(sprintf(__("Duplicate Order ID! Please report this to support@wp-hosting.no together with as much info about the order as possible. Express: %1\$s Status: %2\$s User agent: %3\$s", 'woo-vipps'), $order->get_meta('_vipps_express_checkout'), $order->get_status(), $order->get_customer_user_agent()), 'error'); |
| 1906 |
$order->update_status('cancelled', __('Cannot restart order with same order ID: Must cancel', 'woo-vipps')); |
| 1907 |
} |
| 1908 |
|
| 1909 |
$this->log(sprintf(__('Could not initiate %1$s payment','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage(), 'error'); |
| 1910 |
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'); |
| 1911 |
return []; |
| 1912 |
} |
| 1913 |
|
| 1914 |
$url = $content['url']; |
| 1915 |
$vippstamp = time(); |
| 1916 |
|
| 1917 |
// Ensure we only check the status by ajax of our own orders. IOK 2018-05-03 |
| 1918 |
$sessionorders= WC()->session->get('_vipps_session_orders'); |
| 1919 |
$sessionorders[$order_id] = 1; |
| 1920 |
WC()->session->set('_vipps_session_orders',$sessionorders); |
| 1921 |
WC()->session->set('_vipps_pending_order',$order_id); // Send information to the 'please confirm' screen IOK 2018-04-24 |
| 1922 |
|
| 1923 |
$order = wc_get_order($order_id); |
| 1924 |
if ($authtoken) { |
| 1925 |
$order->update_meta_data('_vipps_authtoken',wp_hash_password($authtoken)); |
| 1926 |
} |
| 1927 |
if ($limited_session) { |
| 1928 |
$order->update_meta_data('_vipps_limited_session',wp_hash_password($limited_session)); |
| 1929 |
} |
| 1930 |
// Store the "session URL" for restarts of the order in the same session context. IOK 2022-11-02 |
| 1931 |
$order->update_meta_data('_vipps_init_timestamp',$vippstamp); |
| 1932 |
$order->update_meta_data('_vipps_orderurl', $url); |
| 1933 |
|
| 1934 |
$order->update_meta_data('_vipps_status','INITIATE'); // INITIATE right now |
| 1935 |
$order->add_order_note(sprintf(__('%1$s payment initiated','woo-vipps'), $this->get_payment_method_name())); |
| 1936 |
$order->add_order_note(sprintf(__('Awaiting %1$s payment confirmation','woo-vipps'), $this->get_payment_method_name())); |
| 1937 |
$order->save(); |
| 1938 |
|
| 1939 |
// Create a signal file that we can check without calling wordpress to see if our result is in IOK 2018-05-04 |
| 1940 |
try { |
| 1941 |
$Vipps->createCallbackSignal($order); |
| 1942 |
} catch (Exception $e) { |
| 1943 |
// Could not create a signal file, but that's ok. |
| 1944 |
} |
| 1945 |
|
| 1946 |
do_action('woo_vipps_before_redirect_to_vipps',$order_id); |
| 1947 |
|
| 1948 |
// This will send us to a receipt page where we will do the actual work. IOK 2018-04-20 |
| 1949 |
return array('result'=>'success','redirect'=>$url); |
| 1950 |
} |
| 1951 |
|
| 1952 |
|
| 1953 |
// This tries to capture a Vipps payment, and resets the status to 'on-hold' if it fails. IOK 2018-05-07 |
| 1954 |
public function maybe_capture_payment($orderid) { |
| 1955 |
$order = wc_get_order($orderid); |
| 1956 |
if (! Vipps::is_vipps_order($order)) return false; |
| 1957 |
$ok = 0; |
| 1958 |
|
| 1959 |
# Shortcut orders that have been directly captured |
| 1960 |
$vippsstatus = $order->get_meta('_vipps_status'); |
| 1961 |
if ($vippsstatus == 'SALE') { |
| 1962 |
return true; |
| 1963 |
} |
| 1964 |
|
| 1965 |
$remaining = intval($order->get_meta('_vipps_capture_remaining')); |
| 1966 |
|
| 1967 |
// Somehow the order status in payment_complete has been set to the 'after order status' or 'complete' by a filter. If so, do not capture. |
| 1968 |
// Capture will be done *before* payment_complete if appropriate IOK 2020-09-22 |
| 1969 |
if (did_action('woocommerce_pre_payment_complete')) { |
| 1970 |
if (!$order->needs_processing()) return; // This is fine, we've captured. |
| 1971 |
if ($remaining>0) { |
| 1972 |
// Not everything has been captured, but we have reached a capturable status. Complain, do not capture. IOK 2020-09-22 |
| 1973 |
$this->log(sprintf(__("Filters are setting the payment_complete order status to '%1\$s' - will not capture", 'woo-vipps'), $order->get_status()),'debug'); |
| 1974 |
$order->add_order_note(sprintf(__('Payment complete set status to "%1$s" - will not capture payments automatically','woo-vipps'), $order->get_status())); |
| 1975 |
return false; |
| 1976 |
} |
| 1977 |
} |
| 1978 |
|
| 1979 |
// IOK 2019-10-03 it is now possible to do capture via other tools than Woo, so we must now first check to see if |
| 1980 |
// the order is capturable by getting full payment details. |
| 1981 |
try { |
| 1982 |
$order = $this->update_vipps_payment_details($order); |
| 1983 |
} catch (Exception $e) { |
| 1984 |
//Do nothing with this for now |
| 1985 |
$this->log(__("Error getting payment details before doing capture: ", 'woo-vipps') . $e->getMessage(), 'warning'); |
| 1986 |
} |
| 1987 |
|
| 1988 |
try { |
| 1989 |
$ok = $this->capture_payment($order); |
| 1990 |
} catch (Exception $e) { |
| 1991 |
// This is handled in sub-methods so we shouldn't actually hit this IOK 2018-05-07 |
| 1992 |
} |
| 1993 |
if ($ok) { |
| 1994 |
// Signal other hooked actions that this one actually did something. IOK 2025-02-04 |
| 1995 |
$order->update_meta_data('_vipps_capture_complete',true); |
| 1996 |
$order->save(); |
| 1997 |
} else { |
| 1998 |
$order->update_meta_data('_vipps_capture_complete',false); |
| 1999 |
$msg = sprintf(__("Could not capture %1\$s payment for this order!", 'woo-vipps'), $this->get_payment_method_name()); |
| 2000 |
$order->add_order_note($msg); |
| 2001 |
$order->save(); |
| 2002 |
if (apply_filters('woo_vipps_on_hold_on_failed_capture', true, $order)) { |
| 2003 |
$msg = sprintf(__("Could not capture %1\$s payment - status set to", 'woo-vipps'), $this->get_payment_method_name()) . ' ' . __('on-hold','woocommerce'); |
| 2004 |
$order->set_status('on-hold',$msg); |
| 2005 |
$order->save(); |
| 2006 |
global $Vipps; |
| 2007 |
$this->adminerr($msg); |
| 2008 |
$Vipps->store_admin_notices(); |
| 2009 |
return false; |
| 2010 |
} |
| 2011 |
} |
| 2012 |
} |
| 2013 |
|
| 2014 |
|
| 2015 |
// Capture (possibly partially) the order. Only full capture really supported by plugin at this point. IOK 2018-05-07 |
| 2016 |
// Except that we *do* note that money "refunded" through vipps before capture should be "uncapturable". IOK 2024-11-25 |
| 2017 |
public function capture_payment($order) { |
| 2018 |
$pm = $order->get_payment_method(); |
| 2019 |
if (! Vipps::is_vipps_order($pm)) { |
| 2020 |
$this->log(sprintf(__('Trying to capture payment on order not made by %1$s:','woo-vipps'), $this->get_payment_method_name()). ' ' . $order->get_id(), 'error'); |
| 2021 |
$this->adminerr(sprintf(__('Cannot capture payment on orders not made by %1$s','woo-vipps'), $this->get_payment_method_name())); |
| 2022 |
return false; |
| 2023 |
} |
| 2024 |
|
| 2025 |
// Partial capture can happen if the order is edited IOK 2017-12-19 |
| 2026 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2027 |
$vippsstatus = $order->get_meta('_vipps_status'); |
| 2028 |
$noncapturable = intval($order->get_meta('_vipps_noncapturable')); // This money has been marked as not-to-be-captured. It will be cancelled on complete. |
| 2029 |
|
| 2030 |
// Ensure 'SALE' direct captured orders work |
| 2031 |
if (!$captured && $vippsstatus == 'SALE') { |
| 2032 |
$order = $this->update_vipps_payment_details($order); |
| 2033 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2034 |
} |
| 2035 |
|
| 2036 |
$total = round(wc_format_decimal($order->get_total(),'')*100); |
| 2037 |
$amount = $total-$captured-$noncapturable; // IOK subtract any amount not to be captured here |
| 2038 |
|
| 2039 |
if ($amount<=0) { |
| 2040 |
$order->add_order_note(__('Payment already captured','woo-vipps')); |
| 2041 |
return true; |
| 2042 |
} |
| 2043 |
|
| 2044 |
// If we already have captured everything, then we are ok! IOK 2017-05-07 |
| 2045 |
if ($captured) { |
| 2046 |
$remaining = intval($order->get_meta('_vipps_capture_remaining')); |
| 2047 |
if (!$remaining) { |
| 2048 |
$order->add_order_note(__('Payment already captured','woo-vipps')); |
| 2049 |
return true; |
| 2050 |
} |
| 2051 |
} |
| 2052 |
|
| 2053 |
// Each time we succeed, we'll increase the 'capture' transaction id so we don't just capture the same amount again and again. IOK 2018-05-07 |
| 2054 |
// (but on failre, we don't increase it - and also, we don't really support partial capture yet.) IOK 2018-05-07 |
| 2055 |
$requestidnr = intval($order->get_meta('_vipps_capture_transid')); |
| 2056 |
// IOK 2023-03-13 keep track of failed captures; because some stores automate capturing without paying attention to the result. |
| 2057 |
// some reservations are for only 7 days (or 30 days or 180 days) so some orders will be uncapturable. This will be reset by the |
| 2058 |
// 'Show full transaction details' metabox. |
| 2059 |
$failures = intval($order->get_meta('_vipps_capture_failures')); |
| 2060 |
$failurelimit = 10; |
| 2061 |
try { |
| 2062 |
|
| 2063 |
// Assume we cannot capture if we have gotten errors 10 times from the API |
| 2064 |
if ($failures >= $failurelimit) { |
| 2065 |
throw new Exception(sprintf(__("More than %1\$d API exceptions trying to capture order - this order cannot be captured.", 'woo-vipps'), $failures)); |
| 2066 |
} |
| 2067 |
|
| 2068 |
$requestid = $requestidnr . ":" . $order->get_order_key(); |
| 2069 |
$api = $order->get_meta('_vipps_api'); |
| 2070 |
|
| 2071 |
|
| 2072 |
if ($api == 'banktransfer') { |
| 2073 |
// This is an error - we should not ever get to the 'capture' branch if we are a banktransfer payment. |
| 2074 |
// IOK 2024-01-09 |
| 2075 |
$content = []; |
| 2076 |
} elseif ($api == 'epayment') { |
| 2077 |
$content = $this->api->epayment_capture_payment($order,$amount,$requestid); |
| 2078 |
} else { |
| 2079 |
$content = $this->api->capture_payment($order,$amount,$requestid); |
| 2080 |
} |
| 2081 |
} catch (TemporaryVippsApiException $e) { |
| 2082 |
$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'); |
| 2083 |
$this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . "\n" . $e->getMessage()); |
| 2084 |
return false; |
| 2085 |
} catch (Exception $e) { |
| 2086 |
// Keep track of API failures up to a point. IOK 2024-03-13 |
| 2087 |
if ($failures < $failurelimit) { |
| 2088 |
$order->update_meta_data('_vipps_capture_failures', $failures + 1); |
| 2089 |
$order->save(); |
| 2090 |
} |
| 2091 |
|
| 2092 |
$msg = sprintf(__('Could not capture %1$s payment for order_id:','woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id() . "\n" . $e->getMessage(); |
| 2093 |
$this->log($msg,'error'); |
| 2094 |
$this->adminerr($msg); |
| 2095 |
return false; |
| 2096 |
} |
| 2097 |
|
| 2098 |
$currency = $order->get_currency(); |
| 2099 |
|
| 2100 |
// Previously, we got this from the transactionInfo field of the Vipps data - this is no longer provided. IOK 2025-08-12 |
| 2101 |
// We simply have to keep track: There is no way of knowing what the correct values are here yet, as we only get these values async, after |
| 2102 |
// the fact. |
| 2103 |
$captured = $amount + intval($order->get_meta('_vipps_captured')); |
| 2104 |
$remaining = intval($order->get_meta('_vipps_amount')) - $captured - intval($order->get_meta('_vipps_cancelled')); |
| 2105 |
$refundable = $captured - intval($order->get_meta('_vipps_refunded')); |
| 2106 |
|
| 2107 |
$order->update_meta_data('_vipps_captured', $captured); |
| 2108 |
$order->update_meta_data('_vipps_capture_remaining', $remaining); |
| 2109 |
$order->update_meta_data('_vipps_refund_remaining', $refundable); |
| 2110 |
$order->update_meta_data('_vipps_capture_timestamp', time()); |
| 2111 |
$order->add_order_note(sprintf(__('%1$s Payment captured:','woo-vipps'), $this->get_payment_method_name()) . ' ' . sprintf("%0.2f",$captured/100) . ' ' . $currency); |
| 2112 |
|
| 2113 |
|
| 2114 |
// Since we succeeded, the next time we'll start a new transaction. |
| 2115 |
$order->update_meta_data('_vipps_capture_transid', $requestidnr+1); |
| 2116 |
$order->save(); |
| 2117 |
|
| 2118 |
return true; |
| 2119 |
} |
| 2120 |
|
| 2121 |
|
| 2122 |
// Cancel (only completely) a reserved but not yet captured order IOK 2018-05-07 |
| 2123 |
public function cancel_payment($order) { |
| 2124 |
$pm = $order->get_payment_method(); |
| 2125 |
if (! Vipps::is_vipps_order($pm)) { |
| 2126 |
$this->log(sprintf(__('Trying to cancel payment on order not made by %1$s:','woo-vipps'), $this->get_payment_method_name()). ' ' .$order->get_id(), 'error'); |
| 2127 |
$this->adminerr(sprintf(__('Cannot cancel payment on orders not made by %1$s','woo-vipps'), $this->get_payment_method_name())); |
| 2128 |
return false; |
| 2129 |
} |
| 2130 |
// We'll use the same transaction id for all cancel jobs, as we can only do it completely. IOK 2018-05-07 |
| 2131 |
// For epayment, partial cancellations will be possible. IOK 2022-11-12 |
| 2132 |
$api = $order->get_meta('_vipps_api'); |
| 2133 |
try { |
| 2134 |
$requestid = ""; |
| 2135 |
if ($api == 'banktransfer') { |
| 2136 |
// If we are here, and the order is somehow not captured, just do nothing. IOK 2024-01-09 |
| 2137 |
$content = []; |
| 2138 |
} elseif ($api == 'epayment') { |
| 2139 |
$requestid = 1; |
| 2140 |
// This will cancel any remaining, not-captured amount IOK 2026-01-28 |
| 2141 |
$content = $this->api->epayment_cancel_payment($order,$requestid); |
| 2142 |
} else { |
| 2143 |
// If we have captured the order, we can't cancel it with the ecom API IOK 2018-05-07 |
| 2144 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2145 |
if ($captured>0) { |
| 2146 |
$msg = sprintf(__('Cannot cancel a captured %1$s transaction - use refund instead', 'woo-vipps'), "ECOM " . $this->get_payment_method_name()); |
| 2147 |
$this->adminerr($msg); |
| 2148 |
return false; |
| 2149 |
} |
| 2150 |
$content = $this->api->cancel_payment($order,$requestid); |
| 2151 |
} |
| 2152 |
} catch (TemporaryVippsApiException $e) { |
| 2153 |
$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'); |
| 2154 |
$this->adminerr(sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage()); |
| 2155 |
return false; |
| 2156 |
} catch (Exception $e) { |
| 2157 |
$msg = sprintf(__('Could not cancel %1$s payment for order id:','woo-vipps'), $this->get_payment_method_name()) . $order->get_id() . "\n" . $e->getMessage(); |
| 2158 |
$this->log($msg,'error'); |
| 2159 |
$this->adminerr($msg); |
| 2160 |
return false; |
| 2161 |
} |
| 2162 |
|
| 2163 |
// the epay v2 API would return transactionInfo and Summary with the result, the new epayment api returns nothing. |
| 2164 |
// Removed epay branch 2025-08-12 IOK |
| 2165 |
$total = intval($order->get_meta('_vipps_amount')); |
| 2166 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2167 |
# $cancelled = $amount + intval($order->get_meta('_vipps_cancelled'); |
| 2168 |
$cancelled = $total; |
| 2169 |
$remaining = $total - $captured - $cancelled; |
| 2170 |
|
| 2171 |
// We need to assume it worked. Also, we can't do partial cancels yet, so just cancel everything. |
| 2172 |
$order->update_meta_data('_vipps_cancel_timestamp',time()); |
| 2173 |
$order->update_meta_data('_vipps_cancelled', $cancelled); |
| 2174 |
$order->update_meta_data('_vipps_cancel_remaining', $remaining); |
| 2175 |
|
| 2176 |
|
| 2177 |
// Set status from Vipps, ignore errors, use statusdata if we have it. |
| 2178 |
try { |
| 2179 |
$status = $this->get_vipps_order_status($order); |
| 2180 |
if ($status) $order->update_meta_data('_vipps_status',$status); |
| 2181 |
$order->add_order_note(sprintf(__('%1$s Payment cancelled:','woo-vipps'), $this->get_payment_method_name())); |
| 2182 |
$order->save(); |
| 2183 |
} catch (Exception $e) { |
| 2184 |
} |
| 2185 |
return true; |
| 2186 |
} |
| 2187 |
|
| 2188 |
// Refund (possibly partially) the captured order. IOK 2018-05-07 |
| 2189 |
// The caller must handle the errors. |
| 2190 |
public function refund_payment($order,$amount=0,$cents=false) { |
| 2191 |
$pm = $order->get_payment_method(); |
| 2192 |
if (! Vipps::is_vipps_order($pm)) { |
| 2193 |
$msg = sprintf(__('Trying to refund payment on order not made by %1$s:','woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id(); |
| 2194 |
$this->log($msg,'error'); |
| 2195 |
throw new VippsAPIException($msg); |
| 2196 |
} |
| 2197 |
|
| 2198 |
|
| 2199 |
// If we haven't captured anything, we can't refund IOK 2017-05-07 |
| 2200 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2201 |
if (!$captured) { |
| 2202 |
$msg = sprintf(__('Trying to refund payment on %1$s payment not captured:','woo-vipps'), $this->get_payment_method_name()). ' ' .$order->get_id(); |
| 2203 |
$this->log($msg,'error'); |
| 2204 |
throw new VippsAPIException($msg); |
| 2205 |
} |
| 2206 |
|
| 2207 |
// Each time we succeed, we'll increase the 'refund' transaction id so we don't just refund the same amount again and again. IOK 2018-05-07 |
| 2208 |
// (but on failre, we don't increase it.) IOK 2018-05-07 |
| 2209 |
$requestidnr = intval($order->get_meta('_vipps_refund_transid')); |
| 2210 |
$requestid = $requestidnr . ":" . $order->get_order_key(); |
| 2211 |
|
| 2212 |
$api = $order->get_meta('_vipps_api'); |
| 2213 |
if ($api == 'banktransfer') { |
| 2214 |
$msg = sprintf(__("Cannot refund bank transfer order %1\$d", 'woo-vipps'), $order->get_id()); |
| 2215 |
$this->log($msg, 'error'); |
| 2216 |
throw new Exception($msg); |
| 2217 |
} elseif ($api == 'epayment') { |
| 2218 |
$content = $this->api->epayment_refund_payment($order,$requestid,$amount,$cents); |
| 2219 |
} else { |
| 2220 |
$content = $this->api->refund_payment($order,$requestid,$amount,$cents); |
| 2221 |
} |
| 2222 |
|
| 2223 |
$currency = $order->get_currency(); |
| 2224 |
|
| 2225 |
// Previously, we got updated transaction info in a transactionInfo field. this is no longer provided, |
| 2226 |
// so we have to do dead reckoning. IOK 2025-08-12 |
| 2227 |
// We simply have to keep track: There is no way of knowing what the correct values are here yet, as we only get these values async, after |
| 2228 |
// the fact. |
| 2229 |
$captured = intval($order->get_meta('_vipps_captured')); |
| 2230 |
|
| 2231 |
if (!$amount) { |
| 2232 |
$amount = wc_format_decimal($order->get_total(),''); |
| 2233 |
$cents = false; |
| 2234 |
} |
| 2235 |
if ($amount && !$cents) { |
| 2236 |
$amount = round($amount * 100); |
| 2237 |
} |
| 2238 |
|
| 2239 |
$refunded_now = $amount; |
| 2240 |
$refunded = intval($order->get_meta('_vipps_refunded')) + $amount; |
| 2241 |
$remaining = $captured - $refunded; |
| 2242 |
|
| 2243 |
$order->update_meta_data('_vipps_refunded', $refunded); |
| 2244 |
$order->update_meta_data('_vipps_refund_remaining', $remaining); |
| 2245 |
$order->update_meta_data('_vipps_refund_timestamp', time()); |
| 2246 |
$order->add_order_note(sprintf(__('%1$s Payment Refunded:','woo-vipps'), $this->get_payment_method_name()) . ' ' . sprintf("%0.2f",$refunded/100) . ' ' . $currency ); |
| 2247 |
|
| 2248 |
// Since we succeeded, the next time we'll start a new transaction. |
| 2249 |
$order->update_meta_data('_vipps_refund_transid', $requestidnr+1); |
| 2250 |
$order->save(); |
| 2251 |
return true; |
| 2252 |
} |
| 2253 |
|
| 2254 |
// Generate a one-time password for certain callbacks, with some backwards compatibility for PHP 5.6 |
| 2255 |
public function generate_authtoken($length=32) { |
| 2256 |
$token=""; |
| 2257 |
if (function_exists('random_bytes')) { |
| 2258 |
$token = bin2hex(random_bytes($length)); |
| 2259 |
} elseif (function_exists('openssl_random_pseudo_bytes')) { |
| 2260 |
$token = bin2hex(openssl_random_pseudo_bytes($length)); |
| 2261 |
} elseif (function_exists('mcrypt_create_iv')) { |
| 2262 |
// These aren't "secure" but they are probably ok for this purpose. IOK 2018-05-18 |
| 2263 |
$indirect = 'mcrypt_create_iv'; // grep-based 7.2 compatibility checkers need to be worked around IOK 2018-10-24 |
| 2264 |
$token = bin2hex($indirect($length)); |
| 2265 |
} else { |
| 2266 |
// Final fallback |
| 2267 |
$token = bin2hex(md5(microtime() . ":" . mt_rand())); |
| 2268 |
} |
| 2269 |
|
| 2270 |
return $token; |
| 2271 |
} |
| 2272 |
|
| 2273 |
// Collapse several statuses to a known list IOK 2019-01-23 |
| 2274 |
// Statuses still in use are annotated. IOK 2025-08-12 |
| 2275 |
public function interpret_vipps_order_status($status) { |
| 2276 |
switch ($status) { |
| 2277 |
case 'INITIATE': // legacy, used by us to indicate a fresh order |
| 2278 |
case 'REGISTER': |
| 2279 |
case 'REGISTERED': |
| 2280 |
case 'CREATED': // Checkout, Epayment |
| 2281 |
return 'initiated'; |
| 2282 |
break; |
| 2283 |
case 'RESERVE': |
| 2284 |
case 'RESERVED': |
| 2285 |
case 'AUTHORISED': |
| 2286 |
case 'AUTHORIZED': // Checkout, Epayment |
| 2287 |
case 'CAPTURED': // Epayment |
| 2288 |
case 'REFUNDED': // epayment - this is probably authorized, because it will have had that state *before* it was refuned. IOK 2025-08-12 |
| 2289 |
return 'authorized'; |
| 2290 |
break; |
| 2291 |
case 'SALE': |
| 2292 |
return 'complete'; |
| 2293 |
break; |
| 2294 |
case 'CANCEL': |
| 2295 |
case 'CANCELLED': // Epayment |
| 2296 |
case 'VOID': |
| 2297 |
case 'AUTOREVERSAL': |
| 2298 |
case 'AUTOCANCEL': |
| 2299 |
case 'AUTO_CANCEL': |
| 2300 |
case 'RESERVE_FAILED': |
| 2301 |
case 'FAILED': |
| 2302 |
case 'REJECTED': |
| 2303 |
case 'TERMINATED': // Checkout, Epayment |
| 2304 |
case 'ABORTED': // Epayment |
| 2305 |
case 'EXPIRED': // Epayment |
| 2306 |
return 'cancelled'; |
| 2307 |
break; |
| 2308 |
} |
| 2309 |
// Default should never happen, but just to ensure we are in our enumeration |
| 2310 |
return "initiated"; |
| 2311 |
} |
| 2312 |
|
| 2313 |
// This does not normally call Vipps, so if you need to refresh status, please use callback_check_order_status first. IOK 2019-01-23 |
| 2314 |
public function check_payment_status($order) { |
| 2315 |
if (!$order) return 'cancelled'; |
| 2316 |
$status = $this->interpret_vipps_order_status($order->get_meta('_vipps_status')); |
| 2317 |
// This can happen if the vipps status is set from the back end for instance. IOK 2020-08-14 |
| 2318 |
if ($order->get_status() == 'pending' && $status != 'initiated') { |
| 2319 |
$this->callback_check_order_status($order); |
| 2320 |
$order = wc_get_order($order->get_id()); // refresh to get the new status IOK 2021-01-20 |
| 2321 |
$status = $this->interpret_vipps_order_status($order->get_meta('_vipps_status')); |
| 2322 |
} |
| 2323 |
return $status; |
| 2324 |
} |
| 2325 |
|
| 2326 |
// Called by callback_check_order_status and handle_callback to handle the situation where |
| 2327 |
// the payment method has been set to something else *after* Vipps has gotten the order. |
| 2328 |
// This happens very rarely for people who use Vipps as an external payment method in Klarna, so |
| 2329 |
// we only do it for orders that match this. IOK 2023-02-03 |
| 2330 |
public function reset_erroneous_payment_method($order) { |
| 2331 |
// This is only called by methods that are Vipps-specific, but still lets be careful not to touch other orders |
| 2332 |
|
| 2333 |
// New 2026-01-05: we now check all other payment methods that aren't vipps, and reset it back to vipps. |
| 2334 |
// The issue was using Klarna Payments and pressing 'back' in the browser, then completing the payment in vipps checkout |
| 2335 |
// the order still had the payment method klarna_payments, since we previously only checked 'kco' = klarna/kustom checkout. LP 2026-01-05 |
| 2336 |
if (! Vipps::is_vipps_order($order) && $order->get_meta("_vipps_orderid")) { |
| 2337 |
$order->set_payment_method('vipps'); |
| 2338 |
$express = $order->get_meta('_vipps_express_checkout'); |
| 2339 |
$checkout = $order->get_meta('_vipps_checkout'); |
| 2340 |
$order->set_payment_method_title('Vipps'); |
| 2341 |
if ($express) $order->set_payment_method_title('Vipps Express Checkout'); |
| 2342 |
if ($checkout) $order->set_payment_method_title('Vipps Checkout'); |
| 2343 |
$order->save(); |
| 2344 |
|
| 2345 |
$msg = sprintf(__("Payment method reset to %1\$s - it had been set to another payment method while completing the order for %2\$d", 'woo-vipps'), $this->get_payment_method_name(), $order->get_id()); |
| 2346 |
$this->log($msg, 'debug'); |
| 2347 |
$order->add_order_note($msg); |
| 2348 |
} |
| 2349 |
} |
| 2350 |
|
| 2351 |
// Check status of order at Vipps, in case the callback has been delayed or failed. |
| 2352 |
// Should only be called if in status 'pending'; it will modify the order when status changes. |
| 2353 |
public function callback_check_order_status($order, $allow_retry = true) { |
| 2354 |
global $Vipps; |
| 2355 |
$orderid = $order->get_id(); |
| 2356 |
|
| 2357 |
clean_post_cache($order->get_id()); |
| 2358 |
$order = wc_get_order($orderid); // Ensure a fresh copy is read. |
| 2359 |
|
| 2360 |
$oldstatus = $order->get_status(); |
| 2361 |
$newstatus = $oldstatus; |
| 2362 |
|
| 2363 |
// Only do work when the orders woo status is pending |
| 2364 |
if ($oldstatus != 'pending') return $oldstatus; |
| 2365 |
|
| 2366 |
$oldvippsstatus = $this->interpret_vipps_order_status($order->get_meta('_vipps_status')); |
| 2367 |
$vippsstatus = ""; |
| 2368 |
|
| 2369 |
/* Now read the payment details and update the order with the relevant values, finding the new Vipps status IOK 2021-01-20 */ |
| 2370 |
$paymentdetails = array(); |
| 2371 |
try { |
| 2372 |
$paymentdetails = $this->get_payment_details($order); |
| 2373 |
$newvippsstatus = $paymentdetails['status']; |
| 2374 |
$vippsstatus = $this->interpret_vipps_order_status($newvippsstatus); |
| 2375 |
|
| 2376 |
$ready = false; // True if money is authorized or complete |
| 2377 |
if (in_array($vippsstatus, ['authorized', 'complete'])) { |
| 2378 |
$ready = true; |
| 2379 |
} |
| 2380 |
|
| 2381 |
// No change == nothing to do. Ensure we don't modify the order at this point. IOK 2025-10-24 |
| 2382 |
if ($vippsstatus == $oldvippsstatus) { |
| 2383 |
return $oldstatus; |
| 2384 |
} |
| 2385 |
// Something changed, so we are now going to sideeffect the order. IOK 2025-10-15 |
| 2386 |
$this->log(sprintf(__("%1\$s poll: Handling order: ", 'woo-vipps'), Vipps::CompanyName()) . " " . $orderid, 'debug'); |
| 2387 |
|
| 2388 |
// If we are in the process of getting a callback from vipps, don't update anything. Currently, Woo/WP has no locking mechanism, |
| 2389 |
// and it isn't feasible to implement one portably. So this reduces somewhat the likelihood of races when this method is called |
| 2390 |
// and callbacks happen at the same time. |
| 2391 |
if (!$Vipps->lockOrder($order)) { |
| 2392 |
return $oldstatus; |
| 2393 |
} |
| 2394 |
|
| 2395 |
// Failsafe for rare bug when using Klarna Checkout with Vipps as an external payment method |
| 2396 |
// IOK 2024-01-09 ensure this is called only when order is complete/authorized |
| 2397 |
if ($ready) { |
| 2398 |
$this->reset_erroneous_payment_method($order); |
| 2399 |
} |
| 2400 |
|
| 2401 |
$order->update_meta_data('_vipps_status',$newvippsstatus); |
| 2402 |
|
| 2403 |
// Extract order metadata from either Checkout or Epayment - set below IOK 2025-08-13 |
| 2404 |
if (!empty($paymentdetails)) { |
| 2405 |
|
| 2406 |
|
| 2407 |
// checkout has a string, epayment has an array with upper case "type" and apparently, cardBin IOK 2025-08-12 |
| 2408 |
$paymentMethod = $paymentdetails['paymentMethod'] ?? "epayment"; |
| 2409 |
// After normalization, all APIs will have data here. |
| 2410 |
$details = $paymentdetails['paymentDetails']; |
| 2411 |
if (isset($details['paymentMethod'])) { |
| 2412 |
$paymentMethod = $details['paymentMethod']; |
| 2413 |
} |
| 2414 |
if (!is_string($paymentMethod)) { |
| 2415 |
// should be WALLET |
| 2416 |
$paymentMethod = $paymentMethod['type'] ?? "epayment"; |
| 2417 |
} |
| 2418 |
$transaction = array(); |
| 2419 |
$transaction['timeStamp'] = date('Y-m-d H:i:s', time()); |
| 2420 |
$transaction['amount'] = $details['amount']['value']; |
| 2421 |
$transaction['currency'] = $details['amount']['currency']; |
| 2422 |
$transaction['status'] = $details['state']; |
| 2423 |
$transaction['paymentmethod'] = $paymentMethod; |
| 2424 |
$this->order_set_transaction_metadata($order, $transaction); |
| 2425 |
} |
| 2426 |
|
| 2427 |
} catch (Exception $e) { |
| 2428 |
$this->log(sprintf(__("Error getting payment details from %1\$s for order_id:",'woo-vipps'), $this->get_payment_method_name()) . $orderid . "\n" . $e->getMessage(), 'error'); |
| 2429 |
clean_post_cache($order->get_id()); |
| 2430 |
$Vipps->unlockOrder($order); |
| 2431 |
return $oldstatus; |
| 2432 |
} |
| 2433 |
$order->save(); |
| 2434 |
|
| 2435 |
// We have a completed order, but the callback haven't given us the payment details yet - so handle it. |
| 2436 |
if (($vippsstatus == 'authorized' || $vippsstatus=='complete') && $order->get_meta('_vipps_express_checkout')) { |
| 2437 |
|
| 2438 |
do_action('woo_vipps_express_checkout_get_order_status', $paymentdetails); |
| 2439 |
$address_set = $order->get_meta('_vipps_shipping_set'); |
| 2440 |
|
| 2441 |
if ($address_set) { |
| 2442 |
// Callback has handled the situation, do nothing |
| 2443 |
} elseif ($paymentdetails['shippingDetails'] ?? "") { |
| 2444 |
// We need to set shipping details here |
| 2445 |
$billing = isset($paymentdetails['billingDetails']) ? $paymentdetails['billingDetails'] : false; |
| 2446 |
$this->set_order_shipping_details($order,$paymentdetails['shippingDetails'], $paymentdetails['userDetails'], $billing, $paymentdetails); |
| 2447 |
} else { |
| 2448 |
// IN THIS CASE we actually need to cancel the order as we have no way of determining whose order this is. |
| 2449 |
// But first check to see if it has customer info! |
| 2450 |
// Cancel any orders where the Checkout session is dead and there is no address info available |
| 2451 |
if (!$order->has_shipping_address() && !$order->has_billing_address()) { |
| 2452 |
$this->log(sprintf(__("No shipping details from %1\$s for express checkout for order id:",'woo-vipps'), $this->get_payment_method_name()) . ' ' . $orderid, 'error'); |
| 2453 |
$sessiontimeout = time() - (60*90); |
| 2454 |
$then = intval($order->get_meta('_vipps_init_timestamp')); |
| 2455 |
if ($then < $sessiontimeout) { |
| 2456 |
$this->log(sprintf(__("Order %2\$d has no address info and any %1\$s session is dead - have to cancel.", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id())); |
| 2457 |
$order->update_status('cancelled', sprintf(__('Could not get address info for order from %1$s', 'woo-vipps'), Vipps::CompanyName())); |
| 2458 |
$order->save(); |
| 2459 |
} else { |
| 2460 |
// NOOP - the vipps checkout session can still be active, so we need to let it be |
| 2461 |
$this->log(sprintf(__("No address information for order %2\$d, but there still might be an active %1\$s session for it, so do not cancel it.", 'woo-vipps'), Vipps::CheckoutName(), $order->get_id())); |
| 2462 |
} |
| 2463 |
} |
| 2464 |
clean_post_cache($order->get_id()); |
| 2465 |
$Vipps->unlockOrder($order); |
| 2466 |
return $oldstatus; |
| 2467 |
} |
| 2468 |
} |
| 2469 |
|
| 2470 |
# Since the order is pending, and the vipps status has changed, switch to the correct vipps status in woo too IOK 2025-10-24 |
| 2471 |
switch ($vippsstatus) { |
| 2472 |
case 'authorized': |
| 2473 |
$this->payment_complete($order); |
| 2474 |
break; |
| 2475 |
case 'complete': |
| 2476 |
$msg = sprintf(__('Payment captured directly at %1$s', 'woo-vipps'), $this->get_payment_method_name()); |
| 2477 |
$msg = $msg . __(" - order does not need processing", 'woo-vipps'); |
| 2478 |
$order->add_order_note($msg); |
| 2479 |
$order = $this->update_vipps_payment_details($order, $paymentdetails); |
| 2480 |
$order->payment_complete(); |
| 2481 |
break; |
| 2482 |
case 'cancelled': |
| 2483 |
$order_is_retryable = $allow_retry && Vipps::order_is_vipps_retryable($order->get_id()); |
| 2484 |
$status_on_fail = $this->get_option('status_on_fail'); |
| 2485 |
$cancel_on_fail = apply_filters('woo_vipps_cancel_failed_orders', false, $order, $vippsstatus); |
| 2486 |
if ($cancel_on_fail || !$order_is_retryable) { |
| 2487 |
$status_on_fail = 'cancelled'; |
| 2488 |
} |
| 2489 |
if (!in_array($status_on_fail, ['cancelled', 'failed'])) { |
| 2490 |
/* translators: order status name. Cancelled is woocommerce status name */ |
| 2491 |
$this->log(__('Unsupported status for payment failure of \'%1$s\', falling back to cancelled.', 'woo-vipps'), 'warning'); |
| 2492 |
$status_on_fail = 'cancelled'; |
| 2493 |
} |
| 2494 |
|
| 2495 |
/* translators: company name */ |
| 2496 |
$order->update_status($status_on_fail, sprintf(__('Order failed or rejected at %1$s.', 'woo-vipps'), Vipps::CompanyName())); |
| 2497 |
break; |
| 2498 |
} |
| 2499 |
|
| 2500 |
$order->save(); |
| 2501 |
clean_post_cache($order->get_id()); |
| 2502 |
$newstatus = $order->get_status(); |
| 2503 |
$Vipps->unlockOrder($order); |
| 2504 |
return $newstatus; |
| 2505 |
} |
| 2506 |
|
| 2507 |
// IOK 2020-01-20 Previously was just a debugging tool, then was used to update postmeta values. Now is used as the main source of info |
| 2508 |
// about the order from Vipps; the previous side-effecting is now done by update_vipps_payment_details. |
| 2509 |
// IOK 2021-11-24 Because of this, we need to handle 402 and 404 errors differently here now - these *are* results, meaning there is |
| 2510 |
// no payment details because the order doesn't exist. |
| 2511 |
// IOK 2022-01-19 And now, with the epayment API in use by checkout, we also need to use the poll api because the epayment API does not return user- and shipping data. We do get an order status though. |
| 2512 |
// IOK 2025-08-12 And now, with epayment used for Express Checkout also, there *is* user- and shipping-data when using Express Checkout, so we can simplify. We still need to transform the data |
| 2513 |
// so old hooks and filters can get the input they expect. |
| 2514 |
public function get_payment_details($order) { |
| 2515 |
$result = array(); |
| 2516 |
$checkout_session = $order->get_meta('_vipps_checkout_session'); |
| 2517 |
$express = $order->get_meta('_vipps_express_checkout'); |
| 2518 |
|
| 2519 |
// IOK 2025-08-12: Three cases; either this is Checkout, in which case we need to get user/shipping-data from the checkout session, |
| 2520 |
// or it is Express Checkout or normal payment, in which cases we just retrieve the payment from the epayment API - possibly containing user data |
| 2521 |
if ($checkout_session) { |
| 2522 |
try { |
| 2523 |
$result = $this->api->checkout_get_session_info($order); |
| 2524 |
|
| 2525 |
if ($result == 'EXPIRED') { |
| 2526 |
$result = array('status'=>'CANCEL', 'state'=>'CANCEL'); |
| 2527 |
return $result; |
| 2528 |
} |
| 2529 |
|
| 2530 |
// For checkout, we are really handling *session states* which we map to *order states*, but sometimes |
| 2531 |
// these indicate a failed order. |
| 2532 |
// 'state' can be missing for Checkout, first case is if this is because the session is invalid. |
| 2533 |
// The sesssion states are # "SessionCreated" "PaymentInitiated" "SessionExpired" "PaymentSuccessful" "PaymentTerminated" |
| 2534 |
if (isset($result['sessionState']) && ($result['sessionState'] == 'SessionTerminated' || $result['sessionState'] == 'SessionExpired')) { |
| 2535 |
$result['state'] = 'CANCEL'; |
| 2536 |
$result['status'] = 'CANCEL'; |
| 2537 |
$order->add_order_note(sprintf(__('%1$s Order with no order status, so session was never completed; setting status to cancelled', 'woo-vipps'), Vipps::CheckoutName())); |
| 2538 |
return $result; |
| 2539 |
} |
| 2540 |
|
| 2541 |
// IOK 2023-12-14 at some point, apparently SessionStarted became SessionCreated |
| 2542 |
if (isset($result['sessionState']) && ($result['sessionState'] == 'SessionStarted' || $result['sessionState'] == 'SessionCreated')) { |
| 2543 |
// We have no order info, only the session data (this is a checkout order). Therefore, assume it has been started at least. |
| 2544 |
$result['status'] = "INITIATE"; |
| 2545 |
$result['state'] = "INITIATE"; |
| 2546 |
$created = $order->get_date_created(); |
| 2547 |
$timestamp = 0; |
| 2548 |
$now = time(); |
| 2549 |
try { |
| 2550 |
$timestamp = $created->getTimestamp(); |
| 2551 |
} catch (Exception $e) { |
| 2552 |
// PHP 8 gives ValueError for certain older versions of WooCommerce here. |
| 2553 |
$timestamp = intval($created->format('U')); |
| 2554 |
} |
| 2555 |
$passed = $now - $timestamp; |
| 2556 |
$minutes = ($passed / 60); |
| 2557 |
|
| 2558 |
// Give up after 120 minutes. Actually, 60 minutes is probably enough: We expire live sessions after 50 mins. |
| 2559 |
if ($minutes > 120) { |
| 2560 |
$this->log(sprintf(__('Checkout order older than 120 minutes with no order status - cancelled as abandoned: %1$s', 'woo-vipps'), $order->get_id()), 'debug'); |
| 2561 |
$order->add_order_note(sprintf(__('%1$s Order with no order status, so session was never completed; setting status to cancelled', 'woo-vipps'), Vipps::CheckoutName())); |
| 2562 |
$result['status'] = 'CANCEL'; |
| 2563 |
$result['state'] = 'CANCEL'; |
| 2564 |
} |
| 2565 |
return $result; |
| 2566 |
} |
| 2567 |
|
| 2568 |
|
| 2569 |
} catch (VippsAPIException $e) { |
| 2570 |
$resp = intval($e->responsecode); |
| 2571 |
if ($resp == 402 || $resp == 404) { |
| 2572 |
$result = array('status'=>'CANCEL', 'state'=>'CANCEL'); |
| 2573 |
return $result; |
| 2574 |
} else { |
| 2575 |
throw $e; |
| 2576 |
} |
| 2577 |
} |
| 2578 |
} else { |
| 2579 |
try { |
| 2580 |
$result = $this->api->epayment_get_payment($order); |
| 2581 |
} catch (VippsAPIException $e) { |
| 2582 |
$resp = intval($e->responsecode); |
| 2583 |
if ($resp == 402 || $resp == 404) { |
| 2584 |
$result = array('status'=>'CANCEL', 'state'=>'CANCEL'); |
| 2585 |
return $result; |
| 2586 |
} else { |
| 2587 |
$this->log(sprintf(__("Could not get order status from %1\$s using epayment api: ", 'woo-vipps'), Vipps::CompanyName()) . $e->getMessage(), "error"); |
| 2588 |
throw $e; |
| 2589 |
} |
| 2590 |
} |
| 2591 |
} |
| 2592 |
|
| 2593 |
if (!$result || $result == "ERROR") { |
| 2594 |
$this->log(sprintf(__("Could not get payment results for order %1\$s", 'woo-vipps'), $order->get_id())); |
| 2595 |
wp_die(sprintf(__("Could not get payment results for order %1\$s - you may have the wrong MSN for the order. Please check logs for more information", 'woo-vipps'), $order->get_id())); |
| 2596 |
} |
| 2597 |
|
| 2598 |
// We now have a result which maybe will contain user and shipping data, which we will need to normalize because it is slightly different in the different |
| 2599 |
// APIs, and we have provided filters/hooks that receive this information. IOK 2025-08-12 |
| 2600 |
// If we now have epayment data, we want to translate this to the ecom 'view' for now. Later, we will do the opposite. |
| 2601 |
// We now only need to map Checkout and epayment to the same format, but we will try to keep the normalized result compatible with |
| 2602 |
// ecom, in case consumers have created filters/hooks. IOK 2025-08-12 |
| 2603 |
|
| 2604 |
// if Checkout, the result will have a sessionState, reference etc, userInfo, shippingDetails, billingDetails and the payment details in a paymentDetails member. |
| 2605 |
// if not, the result will *be* a paymentDetails field, but with user*Details* and shippingDetails added. No billingDetails. |
| 2606 |
// The end result should have state/status (not neccessarily present with Checkout), userDetails, paymentDetails, shippingDetails and billingDetails, and a transactionSummary. |
| 2607 |
// Because that's what the code depending on this has been expecting. IOK 2025-08-12 |
| 2608 |
if (!$checkout_session) { |
| 2609 |
// This should be an ecom result, so move all data (for simplicitys sake) into paymentDetails |
| 2610 |
$result['paymentDetails'] = $result; |
| 2611 |
} |
| 2612 |
// Ensure we get payment details with state + aggregate, which we do not if the payment method is bank transfer for checkout. IOK 2024-01-09 |
| 2613 |
// Also add keys and transform for backwards compatibility. |
| 2614 |
$result = $this->normalizePaymentDetails($result); |
| 2615 |
$newstatus = $this->interpret_vipps_order_status($result['status']); |
| 2616 |
$ready = false; |
| 2617 |
if (in_array($newstatus, ['authorized', 'complete'])) { |
| 2618 |
$ready = true; |
| 2619 |
} |
| 2620 |
|
| 2621 |
|
| 2622 |
// 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. |
| 2623 |
// 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 |
| 2624 |
// 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 |
| 2625 |
// Also we don't get this when the state is different from AUTHORIZED. Especially not ABORTED. |
| 2626 |
// IOK 2025-09-29: This is *no longer the case* . We actually now get userDetails every time we add the relevant scopes, |
| 2627 |
// so this is now probably dead code. |
| 2628 |
if ($ready && $express && !$checkout_session && !isset($result['userDetails'])) { |
| 2629 |
|
| 2630 |
$sub = isset($result['profile']) && isset($result['profile']['sub']) ? $result['profile']['sub'] : null; |
| 2631 |
$userinfo = []; |
| 2632 |
if (!$sub) { |
| 2633 |
// This should never happen, but be prepared |
| 2634 |
$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" ); |
| 2635 |
$order->add_order_note($message); |
| 2636 |
$this->log($message , "error"); |
| 2637 |
} else { |
| 2638 |
// If this happens, the merchant *may* be able to retrieve the information from Vipps so add a note for it. |
| 2639 |
try { |
| 2640 |
$userinfo = $this->api->get_userinfo($sub); |
| 2641 |
} catch (Exception $e) { |
| 2642 |
$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()); |
| 2643 |
$order->add_order_note($message); |
| 2644 |
$this->log($message, 'woo-vipps', "error"); |
| 2645 |
} |
| 2646 |
} |
| 2647 |
if ($userinfo) { |
| 2648 |
$userDetails = array( |
| 2649 |
'email_verified' => $userinfo['email_verified'], |
| 2650 |
'email' => $userinfo['email'], |
| 2651 |
'firstName' => $userinfo['given_name'] ?? '', |
| 2652 |
'lastName' => $userinfo['family_name'] ?? '', |
| 2653 |
'mobileNumber' => $userinfo['phone_number'] ?? '', |
| 2654 |
'phoneNumber' => $userinfo['phone_number'] ?? '', |
| 2655 |
'userId' => $userinfo['phone_number'] ?? '', |
| 2656 |
'sub' => $userinfo['sub'] |
| 2657 |
); |
| 2658 |
|
| 2659 |
$result['userDetails'] = $userDetails; |
| 2660 |
|
| 2661 |
// We may have asked for the address of the customer, so add that too, or a dummy. |
| 2662 |
if (!isset($result['shippingDetails'])) { |
| 2663 |
$countries=new WC_Countries(); |
| 2664 |
$address =[]; |
| 2665 |
$address['addressLine1'] = ""; |
| 2666 |
$address['addressLine2'] = ""; |
| 2667 |
$address['city'] =""; |
| 2668 |
$address['postCode'] = ""; |
| 2669 |
$address['country'] = $countries->get_base_country(); |
| 2670 |
|
| 2671 |
// This uses other keys than both epayment and checkout, but we'll normalize it later. IOK 2025-08-13 |
| 2672 |
if (isset($userinfo['address'])) { |
| 2673 |
$address['addressLine1'] = $userinfo['address']['street_address']; |
| 2674 |
$address['city'] = $userinfo['address']['region']; |
| 2675 |
$address['country'] = $userinfo['address']['country']; |
| 2676 |
$address['postCode'] = $userinfo['address']['postal_code']; |
| 2677 |
} |
| 2678 |
$result['shippingDetails'] = ['address' => $address]; |
| 2679 |
} |
| 2680 |
} |
| 2681 |
} |
| 2682 |
|
| 2683 |
if ($ready && ($express || $checkout_session)) { |
| 2684 |
// For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10 |
| 2685 |
// This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12 |
| 2686 |
$result = $this->ensure_userDetails($result, $order); |
| 2687 |
|
| 2688 |
// 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 |
| 2689 |
// 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 |
| 2690 |
$result = $this->normalizeShippingDetails($result, $order); |
| 2691 |
} |
| 2692 |
|
| 2693 |
return $result; |
| 2694 |
} |
| 2695 |
|
| 2696 |
// IOK 2025-08-12 Normalize the shippingDetails field for backwards compatibility, since this is different from old express, new express and checkout. |
| 2697 |
function normalizeShippingDetails($result, $order) { |
| 2698 |
|
| 2699 |
// We may actually get no shipping details eg. for Checkout when not asking for addresses etc. |
| 2700 |
if (!isset($result['shippingDetails'])) { |
| 2701 |
$result['shippingDetails'] = ['address' => [] ]; |
| 2702 |
} |
| 2703 |
$details = $result['shippingDetails']; |
| 2704 |
|
| 2705 |
$user = $result['userDetails']; // Normalized earlier |
| 2706 |
$address = $details['address'] ?? []; |
| 2707 |
|
| 2708 |
// No address, but we have a list of addresses in the userDetails field. This is ecom with the address scope added. |
| 2709 |
// We will use the first one. |
| 2710 |
if (empty($address) && isset($result['userDetails']) && isset($result['userDetails']['addresses']) && !empty($result['userDetails']['addresses'])) { |
| 2711 |
$address = $result['userDetails']['addresses'][0]; |
| 2712 |
} |
| 2713 |
|
| 2714 |
// Checkout has address info inside shippingDetails, whereas Express Checkout now has it in an address field IOK 2025-08-12 |
| 2715 |
if (empty($address)) { |
| 2716 |
$address['firstName'] = $details['firstName'] ?? ""; |
| 2717 |
$address['lastName'] = $details['lastName'] ?? ""; |
| 2718 |
$address['email'] = $details['email'] ?? ""; |
| 2719 |
$address['mobileNumber'] = $details['phoneNumber'] ?? ""; |
| 2720 |
$address['addressLine1'] = $details['streetAddress'] ?? ""; |
| 2721 |
$address['addressLine2'] = ""; |
| 2722 |
$address['city'] = $details['city'] ?? ""; |
| 2723 |
$address['postCode'] = $details['postalCode'] ?? ""; |
| 2724 |
$address['country'] = $details['country'] ?? ""; |
| 2725 |
} |
| 2726 |
|
| 2727 |
// Need at least a country for VAT so add our own. |
| 2728 |
if (!$address['country']) { |
| 2729 |
$countries=new WC_Countries(); |
| 2730 |
$address['country'] = $countries->get_base_country(); |
| 2731 |
} |
| 2732 |
|
| 2733 |
// Normalize from user details when neccessary (mostly for new express) |
| 2734 |
$address['firstName'] = ($address['firstName'] ?? "") ?: $user['firstName']; |
| 2735 |
$address['lastName'] = ($address['lastName'] ?? "") ?: $user['lastName']; |
| 2736 |
$address['email'] = ($address['email'] ?? "") ?: $user['email']; |
| 2737 |
$address['mobileNumber'] = ($address['mobileNumber'] ?? "") ?: $user['mobileNumber']; |
| 2738 |
|
| 2739 |
// phoneNumber is checkout, mobileNumber is epayment |
| 2740 |
$address['phoneNumber'] = $address['mobileNumber']; |
| 2741 |
// addressline1 and 2 are epayment, streetAddress is checkout |
| 2742 |
$address['streetAddress'] = $address['addressLine1']; |
| 2743 |
// postCode is epayment, postalCode is checkout |
| 2744 |
$address['postalCode'] = $address['postCode']; |
| 2745 |
|
| 2746 |
|
| 2747 |
// Ensure we have 'address' as in Express/epayment |
| 2748 |
$details['address'] = $address; |
| 2749 |
|
| 2750 |
// Name change from Checkout/Express to new Express from MethodId to OptionId IOK 2025-08-12 |
| 2751 |
$details['shippingMethodId'] = $details['shippingMethodId'] ?? ($details['shippingOptionId'] ?? ""); |
| 2752 |
$details['shippingOptionId'] = $details['shippingMethodId']; |
| 2753 |
|
| 2754 |
$result['shippingDetails'] = $details; |
| 2755 |
return $result; |
| 2756 |
} |
| 2757 |
|
| 2758 |
|
| 2759 |
// IOK 2024-01-09 If using Vipps Checkout with the BankTransfer method, which is eg. used in Finland, |
| 2760 |
// we are (currently) not receiving any 'state' or 'aggregate', so add this iff the payment is successful. |
| 2761 |
// The reason for this is that this payment type does not actually use the epayment API at all (!) |
| 2762 |
// Also moved some other compatibility code here - |
| 2763 |
// --- reference used to be orderId |
| 2764 |
// --- state used to be status |
| 2765 |
// --- there used to be a transactionSummary field |
| 2766 |
// --- at one point there was a 'transactionAggregate' instead of an 'aggregate' |
| 2767 |
private function normalizePaymentDetails($result) { |
| 2768 |
// the 'reference' used to be an 'orderId', keep for compatibility. |
| 2769 |
$result['orderId'] = $result['reference']; |
| 2770 |
if (isset($result['sessionState']) && $result['sessionState'] == 'PaymentSuccessful' && $result['paymentMethod'] == 'BankTransfer') { |
| 2771 |
$details = $result['paymentDetails']; |
| 2772 |
$details['state'] = 'SALE'; // Payment is actually complete at this point |
| 2773 |
$aggregate=[]; |
| 2774 |
$aggregate['capturedAmount'] = $details['amount']; |
| 2775 |
$aggregate['authorizedAmount'] = $details['amount']; |
| 2776 |
$aggregate['refundedAmount'] = ['value'=>0, 'currency'=>$details['amount']['currency']]; |
| 2777 |
$aggregate['cancelledAmount'] = ['value'=>0, 'currency'=>$details['amount']['currency']]; |
| 2778 |
$details['aggregate'] = $aggregate; |
| 2779 |
$result['paymentDetails'] = $details; |
| 2780 |
} |
| 2781 |
|
| 2782 |
// Sometimes we get the state at top level, sometimes in paymentDetails. |
| 2783 |
$state = $result['state'] ?? false; |
| 2784 |
|
| 2785 |
// Now, if we don't have payment details, we should have a dead session or something like it. If we do, we can cretae |
| 2786 |
// a normalized result. IOK 2023-12-13 |
| 2787 |
if (isset($result['paymentDetails'])) { |
| 2788 |
$details = $result['paymentDetails']; |
| 2789 |
$state = $state ?: $details['state']; |
| 2790 |
$result['state'] = $state; |
| 2791 |
$details['state'] = $state; |
| 2792 |
|
| 2793 |
# IOK 2022-01-19 for this, the docs and experience does not agree, so check both |
| 2794 |
$aggregate = (isset($details['transactionAggregate'])) ? $details['transactionAggregate'] : $details['aggregate']; |
| 2795 |
|
| 2796 |
// if 'AUTHORISED' and directCapture is set and true, set to SALE which will set the order to complete |
| 2797 |
// IOK 2025-08-12: This is never true anymore - directCapture is never set and the SALE state does not seem to exist. |
| 2798 |
// this was an extra feature for merchants with special products however, so keep the logic just in case. |
| 2799 |
if (($result['state'] == 'AUTHORISED' || $result['state'] == "AUTHORIZED") && isset($result['directCapture']) && $result['directCapture']) { |
| 2800 |
$result['state'] = "SALE"; |
| 2801 |
} |
| 2802 |
|
| 2803 |
# the transactionSummary used to contain the information now present in 'aggregate', so map it back for compatibility. |
| 2804 |
$transactionSummary = array(); |
| 2805 |
// Always NOK at this point, but we also don't care because the order has the currency |
| 2806 |
// IOK 2024-03-22 Now supports other currencies, but we still don't care. |
| 2807 |
$transactionSummary['capturedAmount'] = isset($aggregate['capturedAmount']) ? $aggregate['capturedAmount']['value'] : 0; |
| 2808 |
$transactionSummary['refundedAmount'] = isset($aggregate['refundedAmount']) ? $aggregate['refundedAmount']['value'] : 0; |
| 2809 |
$transactionSummary['cancelledAmount'] =isset($aggregate['cancelledAmount']) ? $aggregate['cancelledAmount']['value'] : 0; |
| 2810 |
$transactionSummary['authorizedAmount'] =isset($aggregate['authorizedAmount']) ? $aggregate['authorizedAmount']['value'] : 0; |
| 2811 |
$transactionSummary['remainingAmountToCapture'] = $transactionSummary['authorizedAmount'] - $transactionSummary['cancelledAmount'] - $transactionSummary['capturedAmount']; |
| 2812 |
$transactionSummary['remainingAmountToRefund'] = $transactionSummary['capturedAmount'] - $transactionSummary['refundedAmount']; |
| 2813 |
// now also reducing remainingAmmountToCancel with cancelledAmount PMB 2024-11-21 |
| 2814 |
$transactionSummary['remainingAmountToCancel'] = $transactionSummary['authorizedAmount'] - $transactionSummary['capturedAmount'] - $transactionSummary['cancelledAmount']; |
| 2815 |
|
| 2816 |
$result['transactionSummary'] = $transactionSummary; |
| 2817 |
} |
| 2818 |
// After this, the result will contain a 'state' which used to be a 'status' - map back for compatibility. |
| 2819 |
// The reason for the method is that in ecom v2 we needed to calculate this from the transaction history. IOK 2025-08-12 |
| 2820 |
$result['status'] = $this->get_payment_status_from_payment_details($result); |
| 2821 |
|
| 2822 |
// No longer used; was used in later versions of ecom to deduce order status. Added for typewise compatibility. |
| 2823 |
$result['transactionLogHistory'] = array(); |
| 2824 |
// The corresponding epayment log. Filled on-demand by debugging code. |
| 2825 |
$result['epaymentLog'] = null; |
| 2826 |
|
| 2827 |
return $result; |
| 2828 |
} |
| 2829 |
|
| 2830 |
// Vipps Checkout v3 does *not* provide userDetails. Vipps Checkout v2 and epayment *does*. But Checkout additionally allows |
| 2831 |
// 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. |
| 2832 |
// 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 |
| 2833 |
// if not, we use shippingDetails. IOK 2023-01-10 |
| 2834 |
// Also, epayment uses mobileNumber and checkout uses phoneNumber, so normalize. |
| 2835 |
public function ensure_userDetails($vippsdata, $order) { |
| 2836 |
$userDetails = []; |
| 2837 |
|
| 2838 |
// If we have userDetails, use it (ecom API with user data requested - Express Checkout |
| 2839 |
if (isset($vippsdata['userDetails'])) { |
| 2840 |
$userDetails = $vippsdata['userDetails']; |
| 2841 |
// 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 |
| 2842 |
$sub = ""; |
| 2843 |
if (isset($vippsdata['profile']) && isset($vippsdata['profile']['sub'])) { |
| 2844 |
$sub = $vippsdata['profile']['sub']; |
| 2845 |
} |
| 2846 |
$userDetails['sub'] = $sub; |
| 2847 |
|
| 2848 |
} else if (isset($vippsdata['billingDetails'])) { |
| 2849 |
// Otherwise this is now Checkout, and we want to get it from billingDetails preferrably |
| 2850 |
$addr = $vippsdata['billingDetails']; |
| 2851 |
$phone = $addr['phoneNumber']; |
| 2852 |
$userDetails = array( |
| 2853 |
'firstName' => $addr['firstName'], |
| 2854 |
'lastName' => $addr['lastName'], |
| 2855 |
'email' => $addr['email'], |
| 2856 |
'phoneNumber' => $phone, |
| 2857 |
// 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 |
| 2858 |
'sub' => "" |
| 2859 |
); |
| 2860 |
|
| 2861 |
// Or use shippingDetails - this is still Checkout |
| 2862 |
} else if (isset($vippsdata['shippingDetails'])) { |
| 2863 |
$addr = $vippsdata['shippingDetails']; |
| 2864 |
$phone = $addr['phoneNumber']; |
| 2865 |
$userDetails = array( |
| 2866 |
'firstName' => $addr['firstName'], |
| 2867 |
'lastName' => $addr['lastName'], |
| 2868 |
'email' => $addr['email'], |
| 2869 |
'phoneNumber' => $phone, |
| 2870 |
// 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 |
| 2871 |
'sub' => "" |
| 2872 |
); |
| 2873 |
|
| 2874 |
// And it is possible to not require user details in Checkout at all (or if not using express) |
| 2875 |
} else { |
| 2876 |
$userDetails = array( |
| 2877 |
|
| 2878 |
'firstName' => apply_filters('woo_vipps_anon_customer_first_name', __('Anonymous customer', 'woo-vipps'), $order), |
| 2879 |
'lastName' => apply_filters('woo_vipps_anon_customer_last_name', "", $order), |
| 2880 |
'email' => apply_filters('woo_vipps_anon_customer_email', '', $order), |
| 2881 |
'phoneNumber' => apply_filters('woo_vipps_anon_customer_phone_number', '', $order), |
| 2882 |
'sub' => "" |
| 2883 |
); |
| 2884 |
} |
| 2885 |
|
| 2886 |
//Normalize the result |
| 2887 |
$phone = $userDetails['phoneNumber'] ?? ($userDetails['mobileNumber'] ?? ""); |
| 2888 |
$userDetails['phoneNumber'] = $phone; |
| 2889 |
$userDetails['mobileNumber'] = $phone; |
| 2890 |
|
| 2891 |
|
| 2892 |
// No longer used, but try to provide it for backwards compatibility |
| 2893 |
$userDetails['userId'] = $userDetails['phoneNumber']; |
| 2894 |
// This is possible to get iff we have the 'sub', unfortunately it is not passed directly in several of the apis. IOK 2025-08-12 |
| 2895 |
$userDetails['email_verified'] = ($userDetails['email_verified'] ?? false); |
| 2896 |
|
| 2897 |
$vippsdata['userDetails'] = $userDetails; |
| 2898 |
|
| 2899 |
return $vippsdata; |
| 2900 |
} |
| 2901 |
|
| 2902 |
// Update the order with Vipps payment details, either passed or called using the API. |
| 2903 |
public function update_vipps_payment_details ($order, $details = null) { |
| 2904 |
if (!$details) { |
| 2905 |
try { |
| 2906 |
$details = $this->get_payment_details($order); |
| 2907 |
} catch (Exception $e) { |
| 2908 |
// We'll try but if we fail, that's too bad. IOK 2026-03-18. No recovery is really possible here. |
| 2909 |
$this->log(sprintf(__("Could not get payment results for order %1\$s", 'woo-vipps'), $order->get_id())); |
| 2910 |
$this->log($e->getMessage()); |
| 2911 |
} |
| 2912 |
} |
| 2913 |
|
| 2914 |
if ($details) { |
| 2915 |
if (isset($details['transactionSummary'])) { |
| 2916 |
$transactionSummary= $details['transactionSummary']; |
| 2917 |
$order->update_meta_data('_vipps_status',$details['status']); |
| 2918 |
$order->update_meta_data('_vipps_captured',$transactionSummary['capturedAmount']); |
| 2919 |
$order->update_meta_data('_vipps_refunded',$transactionSummary['refundedAmount']); |
| 2920 |
$order->update_meta_data('_vipps_capture_remaining',$transactionSummary['remainingAmountToCapture']); |
| 2921 |
$order->update_meta_data('_vipps_refund_remaining',$transactionSummary['remainingAmountToRefund']); |
| 2922 |
if (isset($details['transactionSummary']['cancelledAmount'])) { |
| 2923 |
$order->update_meta_data('_vipps_cancelled',$transactionSummary['cancelledAmount']); |
| 2924 |
$order->update_meta_data('_vipps_cancel_remaining',$transactionSummary['remainingAmountToCancel']); |
| 2925 |
} |
| 2926 |
} |
| 2927 |
// This is the epayment API - IOK 2022-01-20 |
| 2928 |
if (isset($details['paymentDetails'])) { |
| 2929 |
$d = $details['paymentDetails']; |
| 2930 |
if (isset($d['amount'])) { |
| 2931 |
$order->update_meta_data('_vipps_amount', $d['amount']['value']); |
| 2932 |
} |
| 2933 |
$aggregate = (isset($d['transactionAggregate'])) ? $d['transactionAggregate'] : $d['aggregate']; |
| 2934 |
if ($aggregate) { |
| 2935 |
// capturedAmount, refundedAmount, authorizedAmount, cancelledAmount |
| 2936 |
if (isset($aggregate['authorizedAmount'])) { |
| 2937 |
$order->update_meta_data('_vipps_amount', $aggregate['authorizedAmount']['value']); |
| 2938 |
} |
| 2939 |
} |
| 2940 |
} |
| 2941 |
// Modify payment method name if neccessary |
| 2942 |
if (isset($details['paymentMethod']) && $details['paymentMethod'] == 'Card') { |
| 2943 |
if ($order->get_meta('_vipps_checkout')) { |
| 2944 |
$order->set_payment_method_title(sprintf(__('Credit Card / %1$s', 'woo-vipps'), Vipps::CheckoutName())); |
| 2945 |
} |
| 2946 |
} |
| 2947 |
if (isset($details['paymentMethod']) && $details['paymentMethod'] == 'BankTransfer') { |
| 2948 |
if ($order->get_meta('_vipps_checkout')) { |
| 2949 |
$order->set_payment_method_title(sprintf(__('Bank Transfer/ %1$s', 'woo-vipps'), Vipps::CheckoutName())); |
| 2950 |
$order->update_meta_data('_vipps_api', 'banktransfer'); |
| 2951 |
} |
| 2952 |
} |
| 2953 |
$order->save(); |
| 2954 |
} |
| 2955 |
return $order; |
| 2956 |
} |
| 2957 |
|
| 2958 |
// IOK 2021-01-20 from March 2021 the order_status interface is removed; so we now need to interpret the payment history to find |
| 2959 |
// out the order status. |
| 2960 |
// IOK 2022-01-19 With the epayment API on the other hand, the payment status is back as 'state'. |
| 2961 |
// IOK 2025-08-12 Except for certain payment methods in Checkout. So. |
| 2962 |
// IOK 2025-08-12 Removing the ecom support now, so all code will use 'state', as normalized by normalizePaymentDetails |
| 2963 |
public function get_payment_status_from_payment_details($details) { |
| 2964 |
$status = $details['state']; |
| 2965 |
return $status; |
| 2966 |
} |
| 2967 |
|
| 2968 |
// Get the order status as defined by Vipps; if you have the payment details already, pass them. Will modify the order. IOK 2021-01-20 |
| 2969 |
public function get_vipps_order_status($order, $statusdata=null) { |
| 2970 |
$vippsorderid = $order->get_meta('_vipps_orderid'); |
| 2971 |
if (!$vippsorderid) { |
| 2972 |
$msg = sprintf(__('Could not get %1$s order status - it has no %1$s Order Id. Must cancel.','woo-vipps'), $this->get_payment_method_name()); |
| 2973 |
$this->log($msg,'error'); |
| 2974 |
return 'CANCEL'; |
| 2975 |
} |
| 2976 |
if (!$statusdata) { |
| 2977 |
try { |
| 2978 |
$statusdata = $this->get_payment_details($order); |
| 2979 |
} catch (TemporaryVippsApiException $e) { |
| 2980 |
$this->log(sprintf(__('Could not get %1$s order status for order id:', 'woo-vipps'), Vipps::CompanyName()) . ' ' . $order->get_id() . "\n" .$e->getMessage(),'error'); |
| 2981 |
return null; |
| 2982 |
} catch (VippsAPIException $e) { |
| 2983 |
$msg = sprintf(__('Could not get %1$s order status','woo-vipps'), $this->get_payment_method_name()) . ' ' . $e->getMessage(); |
| 2984 |
$this->log($msg,'error'); |
| 2985 |
if (intval($e->responsecode) == 402 || intval($e->responsecode) == 404) { |
| 2986 |
$this->log(sprintf(__('Order does not exist at %1$s - cancelling','woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id(), 'warning'); |
| 2987 |
return 'CANCEL'; |
| 2988 |
} |
| 2989 |
} catch (Exception $e) { |
| 2990 |
$msg = sprintf(__('Could not get %1$s order status for order id:','woo-vipps'), $this->get_payment_method_name()) . ' ' . $order->get_id() . "\n" . $e->getMessage(); |
| 2991 |
$this->log($msg,'error'); |
| 2992 |
return null; |
| 2993 |
} |
| 2994 |
} |
| 2995 |
if (!$statusdata) return null; |
| 2996 |
|
| 2997 |
$vippsstatus = isset($statusdata['status']) ? $statusdata['status'] : ""; |
| 2998 |
|
| 2999 |
if (!$vippsstatus) { |
| 3000 |
$this->log("Unknown Vipps Status: " . print_r($statusdata, true), 'debug'); |
| 3001 |
} |
| 3002 |
return $vippsstatus; |
| 3003 |
} |
| 3004 |
|
| 3005 |
// The various Vipps APIs return address info with various keys and formats, so we need to translate all of them |
| 3006 |
// to a canonical format. |
| 3007 |
public function canonicalize_vipps_address($address, $user) { |
| 3008 |
// eCom has user info only in the user struct |
| 3009 |
$firstname = $user['firstName']; |
| 3010 |
$lastname = $user['lastName']; |
| 3011 |
$email = $user['email']; |
| 3012 |
|
| 3013 |
// Get the passed phone number from checkout or express, which could be in any number of slots IOK 2025- |
| 3014 |
$phone = isset($user['mobileNumber']) ? $user['mobileNumber'] : ""; |
| 3015 |
if (isset($user['phoneNumber'])) $phone = $user['phoneNumber']; |
| 3016 |
if (!$phone && ($address['phoneNumber'] ?? "")) $phone = $address['phoneNumber']; |
| 3017 |
if (!$phone && ($address['mobileNumber'] ?? "")) $phone = $address['mobileNumber']; |
| 3018 |
|
| 3019 |
// Phone number transformations - the format Vipps Mobilepay uses is often not what merchants expect or need |
| 3020 |
// NOTE: as of writing this, the checkout+expresscheckout expected format is '{countrycode}{phonenr}', so we assume this. LP 2025-12-29 |
| 3021 |
$phone_transformation = $this->get_option('checkout_phone_transformation'); |
| 3022 |
switch($phone_transformation) { |
| 3023 |
case 'ensure_plus': |
| 3024 |
$phone = "+$phone"; |
| 3025 |
break; |
| 3026 |
case 'strip_country_code': |
| 3027 |
// We only support norway,denmark,swedish,finnish country codes as of now. LP 2025-12-29 |
| 3028 |
// We can't do these replaces in sequence, because e.g. +47 46 12 34 56 would become 0 12 34 56 instead of the correct 46 12 34 56. LP 2026-02-19 |
| 3029 |
if (preg_match('!^(45|47)!', $phone)) { // NO, DK |
| 3030 |
$phone = preg_replace('!^(45|47)!', '', $phone); |
| 3031 |
} else if (preg_match('!^(46|358)!', $phone)) { // SE, FI: leading zero for area codes is not used with country code, so add it back here. LP 2026-02-09 |
| 3032 |
$phone = preg_replace('!^(46|358)!', '0', $phone); |
| 3033 |
} |
| 3034 |
break; |
| 3035 |
} |
| 3036 |
$phone = apply_filters('woo_vipps_canonicalize_checkout_phone', $phone, $address, $user); |
| 3037 |
|
| 3038 |
if (!isset($address['firstName']) or !$address['firstName']) { |
| 3039 |
$address['firstName'] = $firstname; |
| 3040 |
} |
| 3041 |
if (!isset($address['lastName']) or !$address['lastName']) { |
| 3042 |
$address['lastName'] = $lastname; |
| 3043 |
} |
| 3044 |
// checkout uses phonenumber |
| 3045 |
if (!isset($address['phoneNumber']) or !$address['phoneNumber']) { |
| 3046 |
$address['phoneNumber'] = $phone; |
| 3047 |
} |
| 3048 |
// epayment uses mobileNumber |
| 3049 |
if (!isset($address['mobileNumber']) or !$address['mobileNumber']) { |
| 3050 |
$address['mobileNumber'] = $phone; |
| 3051 |
} |
| 3052 |
if (!isset($address['email']) or !$address['email']) { |
| 3053 |
$address['email'] = $email; |
| 3054 |
} |
| 3055 |
|
| 3056 |
// epayment |
| 3057 |
$addressline1 = isset($address['addressLine1']) ? $address['addressLine1'] : ""; |
| 3058 |
$addressline2 = isset($address['addressLine2']) ? $address['addressLine2'] : ""; |
| 3059 |
// checkout |
| 3060 |
if (isset($address['streetAddress'])) { |
| 3061 |
$addressline1 = $address['streetAddress']; |
| 3062 |
} |
| 3063 |
if (isset($address['street_address'])) { // From the userinfo api |
| 3064 |
$addressline1 = $address['street_address']; |
| 3065 |
} |
| 3066 |
if ($addressline1 == $addressline2) $addressline2 = ''; |
| 3067 |
$address['addressLine1'] = $addressline1; // epayment |
| 3068 |
$address['addressline2'] = $addressline2; |
| 3069 |
$address['streetAddress'] = $addressline1; // Checkout |
| 3070 |
|
| 3071 |
$city = ""; |
| 3072 |
if (isset($address['city'])) { // checkout and epayment |
| 3073 |
$city = $address['city']; |
| 3074 |
} elseif (isset($address['region'])) { // ecom |
| 3075 |
$city = $address['region']; |
| 3076 |
} |
| 3077 |
$address['city'] = $city; |
| 3078 |
$address['region'] = $city; |
| 3079 |
|
| 3080 |
$postcode= ""; |
| 3081 |
if (isset($address['postCode'])) { |
| 3082 |
$postcode= $address['postCode']; // epayment |
| 3083 |
} elseif (isset($address['postalCode'])){ |
| 3084 |
$postcode= $address['postalCode']; // checkout |
| 3085 |
} elseif (isset($address['postal_code'])) { |
| 3086 |
$postcode= $address['postal_code']; // userinfo |
| 3087 |
} |
| 3088 |
$address['postCode'] = $postcode; // epayment |
| 3089 |
$address['zipCode'] = $postcode; // ecom |
| 3090 |
$address['postalCode'] = $postcode; // checkout |
| 3091 |
|
| 3092 |
// Allow users to modify the address to e.g. handle phone numbers differently IOK 2025-01-20 |
| 3093 |
// note: added separate filter for phone number 'woo_vipps_canonicalize_checkout_phone' because of the different uses, keys etc. LP 2025-12-29 |
| 3094 |
return apply_filters('woo_vipps_canonicalize_checkout_address', $address, $user); |
| 3095 |
} |
| 3096 |
|
| 3097 |
public function set_order_shipping_details($order,$shipping, $user, $billing=false, $alldata=null, $assigncustomer=true) { |
| 3098 |
global $Vipps; |
| 3099 |
$done = $order->get_meta('_vipps_shipping_set'); |
| 3100 |
if ($done) return true; |
| 3101 |
$order->update_meta_data('_vipps_shipping_set', true); |
| 3102 |
$order->save(); // Limit the window for double shipping as much as possible. |
| 3103 |
|
| 3104 |
// This is for handling a custom consent checkbox for mailing lists etc. IOK 2023-02-09 |
| 3105 |
if ($alldata && isset($alldata['customConsentProvided'])) { |
| 3106 |
$order->update_meta_data('_vipps_custom_consent_provided', intval($alldata['customConsentProvided'])); |
| 3107 |
} |
| 3108 |
|
| 3109 |
|
| 3110 |
// We get different values from the normal callback and the Checkout callback, so be prepared for several results. IOK 2021-09-02 |
| 3111 |
// IOK VERIFY this should be normalized now or soon at any rate |
| 3112 |
$address = isset($shipping['address']) ? $shipping['address'] : $shipping;; |
| 3113 |
|
| 3114 |
// Sometimes we get an empty shipping address! In this case, fill the details with the billing address. |
| 3115 |
// This only happens with ecommerce though, so we need to check before canonicalizing. IOK 2022-03-21 |
| 3116 |
// billing is only present in Checkout, which uses phoneNumber, streetAddress, postalCode etc. |
| 3117 |
$shipping_empty = true; |
| 3118 |
if ($billing && array_key_exists('streetAddress', $address)) { |
| 3119 |
$keys = ['firstName', 'lastName', 'email', 'phoneNumber', 'streetAddress', 'postalCode', 'city', 'country']; |
| 3120 |
foreach ($keys as $key) { |
| 3121 |
if (isset($address[$key]) && $address[$key]) { |
| 3122 |
$shipping_empty = false; break; |
| 3123 |
} |
| 3124 |
} |
| 3125 |
if ($shipping_empty) { |
| 3126 |
foreach ($keys as $key) $address[$key] = $billing[$key]; |
| 3127 |
} |
| 3128 |
} |
| 3129 |
|
| 3130 |
if (!$billing) $billing = $address; |
| 3131 |
$address = $this->canonicalize_vipps_address($address, $user); |
| 3132 |
$billing = $this->canonicalize_vipps_address($billing, $user); |
| 3133 |
|
| 3134 |
# Billing. |
| 3135 |
$order->set_billing_email($billing['email']); |
| 3136 |
$order->set_billing_phone($billing['mobileNumber']); |
| 3137 |
$order->set_billing_first_name($billing['firstName']); |
| 3138 |
$order->set_billing_last_name($billing['lastName']); |
| 3139 |
$order->set_billing_address_1($billing['addressLine1']); |
| 3140 |
if ($billing['addressLine2'] ?? false) $order->set_billing_address_2($billing['addressLine2']); |
| 3141 |
$order->set_billing_city($billing['city'] ?? ""); |
| 3142 |
$order->set_billing_postcode($billing['postCode'] ?? ""); |
| 3143 |
$order->set_billing_country($billing['country'] ?? ""); |
| 3144 |
|
| 3145 |
# Shipping. |
| 3146 |
$order->set_shipping_first_name($address['firstName']); |
| 3147 |
$order->set_shipping_last_name($address['lastName']); |
| 3148 |
if (version_compare(WC_VERSION, '5.6.0', '>=')) { |
| 3149 |
$order->set_shipping_phone($address['mobileNumber']); |
| 3150 |
} |
| 3151 |
$order->set_shipping_address_1($address['addressLine1']); |
| 3152 |
if ($address['addressLine2'] ?? "") $order->set_shipping_address_2($address['addressLine2']); |
| 3153 |
$order->set_shipping_city($address['city']); |
| 3154 |
$order->set_shipping_postcode($address['postCode']); |
| 3155 |
$order->set_shipping_country($address['country']); |
| 3156 |
|
| 3157 |
$order->save(); |
| 3158 |
|
| 3159 |
// This is *essential* to get VAT calculated correctly. That calculation uses the customer, which uses the session, which we will have restored at this point.IOK 2019-10-25 |
| 3160 |
if (WC()->customer) { |
| 3161 |
WC()->customer->set_billing_email($billing['email']); |
| 3162 |
WC()->customer->set_email($billing['email']); |
| 3163 |
|
| 3164 |
$country = $billing['country'] ?? ($address['country'] ?? ""); |
| 3165 |
WC()->customer->set_billing_location($country,'',$billing['postalCode'],$billing['region']); |
| 3166 |
WC()->customer->set_shipping_location($address['country'],'',$address['postalCode'],$address['region']); |
| 3167 |
} |
| 3168 |
|
| 3169 |
// We may need the order total early on, so start with that |
| 3170 |
$ordertotal = $order->get_total() ?: 0; |
| 3171 |
|
| 3172 |
$vipps_reserved = $alldata['paymentDetails']['amount']['value'] ?? null; |
| 3173 |
// i dont expect order total to ever be greater than vipps reserved total here, so i dont use abs(). LP 2026-01-28 |
| 3174 |
$diff_reserved_ordertotal = is_numeric($vipps_reserved) ? ($vipps_reserved / 100 - $ordertotal) : 0; |
| 3175 |
|
| 3176 |
// Now do shipping, if it exists IOK 2021-09-02 |
| 3177 |
$method = isset($shipping['shippingMethodId']) ? $shipping['shippingMethodId'] : false; |
| 3178 |
|
| 3179 |
// We will add a shipping rate either if there has been passed a shipping method; or if this |
| 3180 |
// is an order that needs shipping and the amount reserved at vipps is greater than the order total |
| 3181 |
// (with a bit of tolerance for rounding errors). If we *don't* have a shipping method but we know |
| 3182 |
// we need a shipping rate; this is an error condition that the merchant needs to resolve. We'll add a fake |
| 3183 |
// 'Unknown' rate to ensure we capture the right amount and add this fact to the order log. Code by LP, comment by IOK 2026-01-28 |
| 3184 |
$needs_shipping = $method || $order->get_meta('_vipps_needs_shipping'); |
| 3185 |
$tol = 0.01; |
| 3186 |
$has_shipping = $method || $diff_reserved_ordertotal > $tol; |
| 3187 |
|
| 3188 |
if ($needs_shipping && $has_shipping) { |
| 3189 |
$shipping_rate=null; |
| 3190 |
$option_table = []; |
| 3191 |
|
| 3192 |
// Try to find the shipping rate. LP 2026-01-28 |
| 3193 |
if ($method) { |
| 3194 |
if (substr($method,0,1) != '$') { |
| 3195 |
// This is the old way of adding shipping. We should probably deprecate this sometime soon. IOK 2026-01-28 |
| 3196 |
$shipping_rate = $this->get_legacy_express_checkout_shipping_rate($shipping); |
| 3197 |
} else { |
| 3198 |
// Strip suffixes if we have several Express rates mapping to the same Woo rate (eg. for Posten). IOK 2025-05-04 |
| 3199 |
$matches = []; |
| 3200 |
preg_match("!^(?P<key>[^:]+):?(?P<option_index>.+)?$!", $method, $matches); |
| 3201 |
$key = $matches['key'] ?? ""; |
| 3202 |
$option_index = intval(trim($matches['option_index'] ?? "")); // 0 is never an index |
| 3203 |
$shipping_table = $order->get_meta('_vipps_express_checkout_shipping_method_table'); |
| 3204 |
$is_base64 = $shipping_table ? ( $shipping_table['_is_base64'] ?? false) : false; |
| 3205 |
|
| 3206 |
if (is_array($shipping_table) && isset($shipping_table[$key])) { |
| 3207 |
$decoded = $is_base64 ? @base64_decode($shipping_table[$key]) : $shipping_table[$key]; |
| 3208 |
$shipping_rate = $decoded ? @unserialize($decoded) : null; |
| 3209 |
if (!$shipping_rate) { |
| 3210 |
$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'); |
| 3211 |
$this->log(sprintf(__("Serialized data was %1\$s", 'woo-vipps'), $decoded), 'error'); |
| 3212 |
} else { |
| 3213 |
// Special-case the Woo pickup location methods. IOK 2026-01-28 |
| 3214 |
if ($option_index) { |
| 3215 |
$meta = $shipping_rate->get_meta_data(); |
| 3216 |
$option_table = $meta['_vipps_pickupPoints'] ?? []; |
| 3217 |
// force string table IOK 2025-08-15 |
| 3218 |
$point = $option_table["i".$option_index] ?? ""; |
| 3219 |
if ($point) { |
| 3220 |
$shipping['pickupPoint'] = $point; |
| 3221 |
} |
| 3222 |
$shipping_rate->add_meta_data('_vipps_pickupPoints', null); |
| 3223 |
} |
| 3224 |
// Empty this when done, but not if there was an error - let the merchant be able to debug. IOK 2020-02-14 |
| 3225 |
$order->update_meta_data('_vipps_express_checkout_shipping_method_table', null); |
| 3226 |
} |
| 3227 |
} |
| 3228 |
} |
| 3229 |
|
| 3230 |
// Possible extra metadata from Vipps Checkout IOK 2023-01-17 |
| 3231 |
// Store in the order, but also in the shipping rate so it will be visible in the order screen |
| 3232 |
// along with the shipping ragte |
| 3233 |
if (isset($shipping['pickupPoint'])) { |
| 3234 |
$order->update_meta_data('vipps_checkout_pickupPoint', $shipping['pickupPoint']); |
| 3235 |
if ($shipping_rate) { |
| 3236 |
$pp = $shipping['pickupPoint']; |
| 3237 |
$addr = []; |
| 3238 |
foreach(['address', 'postalCode', 'city', 'country'] as $key) { |
| 3239 |
$v = trim($pp[$key]); |
| 3240 |
if (!empty($v)) $addr[] = trim($pp[$key]); |
| 3241 |
} |
| 3242 |
|
| 3243 |
$shipping_rate->add_meta_data('pickup_location', $pp['name']); |
| 3244 |
$shipping_rate->add_meta_data('pickup_address', join(", ", $addr)); |
| 3245 |
$shipping_rate->add_meta_data('pickup_details', ""); // Not supported by the API unfortunately. IOK 2025-06-07 |
| 3246 |
} |
| 3247 |
} |
| 3248 |
if (isset($shipping['timeslot'])) { |
| 3249 |
$order->update_meta_data('vipps_checkout_timeslot', $shipping['timeslot']); |
| 3250 |
if ($shipping_rate) { |
| 3251 |
$pp = $shipping['timeslot']; |
| 3252 |
$slot = ""; |
| 3253 |
$slot .= sprintf(__("Date: %s", 'woo-vipps'), ($pp['date'] ?? "")); |
| 3254 |
$slot .= " " . sprintf(__("Start: %s", 'woo-vipps'), ($pp['start'] ?? "")); |
| 3255 |
$slot .= " " . sprintf(__("End: %s", 'woo-vipps'), ($pp['end'] ?? "")); |
| 3256 |
$shipping_rate->add_meta_data('vipps_delivery_timeslot', $slot); |
| 3257 |
$shipping_rate->add_meta_data('vipps_delivery_timeslot_id', $pp['id']); |
| 3258 |
} |
| 3259 |
} |
| 3260 |
} |
| 3261 |
|
| 3262 |
// We know we need a shipping rate, and what its price has to be, so ensure we have one by making one up if missing. LP 2026-01-28 |
| 3263 |
if (!$shipping_rate) { |
| 3264 |
$shipping_rate = new WC_Shipping_Rate( |
| 3265 |
'UNKNOWN', |
| 3266 |
sprintf(__('Unknown shipping: please check the shipping details at %1$s', 'woo-vipps'), Vipps::CompanyName()), |
| 3267 |
$diff_reserved_ordertotal, |
| 3268 |
[], |
| 3269 |
'UNKNOWN', |
| 3270 |
0, |
| 3271 |
); |
| 3272 |
$shipping_rate = apply_filters('woo_vipps_unknown_shipping_rate_dummy', $shipping_rate, $order); |
| 3273 |
|
| 3274 |
// Add an error log for the merchants to quote to us. |
| 3275 |
$msg = sprintf(__("%1\$s Could not retrieve any shipping rate for this order, with method %2\$s",'woo-vipps'), $this->get_payment_method_name(), $method) . " " . $order->get_id(); |
| 3276 |
$order->add_order_note($msg); |
| 3277 |
$this->log($msg, 'warning'); |
| 3278 |
} |
| 3279 |
|
| 3280 |
$shipping_rate = apply_filters('woo_vipps_express_checkout_final_shipping_rate', $shipping_rate, $order, $shipping); |
| 3281 |
|
| 3282 |
$total_shipping = 0; |
| 3283 |
$total_shipping_tax = 0; |
| 3284 |
$it = null; |
| 3285 |
|
| 3286 |
// Recover the Shipping Method class |
| 3287 |
$methods_classes = WC()->shipping->get_shipping_method_class_names(); |
| 3288 |
$methodclass = $methods_classes[$shipping_rate->get_method_id()] ?? null; |
| 3289 |
$shipping_method = $methodclass ? new $methodclass($shipping_rate->get_instance_id()) : null; |
| 3290 |
$is_vipps_checkout_shipping = $shipping_method && is_a($shipping_method, 'VippsCheckout_Shipping_Method'); |
| 3291 |
|
| 3292 |
// Some Vipps Checkout-specific shipping methods calculate the cost in the Vipps window. |
| 3293 |
if ($is_vipps_checkout_shipping && $shipping_method->dynamic_cost) { |
| 3294 |
$vippsamount = intval($order->get_meta('_vipps_amount')); |
| 3295 |
$shipping_tax_rate = floatval($order->get_meta('_vipps_shipping_tax_rates')); |
| 3296 |
$compareamount = $ordertotal * 100; |
| 3297 |
$amountdiff = $vippsamount-$compareamount; // this is the *actual* shipping cost at this point |
| 3298 |
$diffnotax = ($amountdiff / (100 + $shipping_tax_rate)); // Adjusted to correct values actually |
| 3299 |
$difftax = WC_Tax::round($amountdiff/100 - $diffnotax); |
| 3300 |
$actual = $amountdiff/100 - $difftax; |
| 3301 |
|
| 3302 |
$shipping_rate->set_cost($actual); |
| 3303 |
$shipping_rate->set_taxes( [ 1 => $difftax] ); |
| 3304 |
} |
| 3305 |
|
| 3306 |
$it = new WC_Order_Item_Shipping(); |
| 3307 |
$it->set_shipping_rate($shipping_rate); |
| 3308 |
$it->set_order_id( $order->get_id() ); |
| 3309 |
// This should actually have been done by the "set_shipping_rate" call above, but as of 3.9.2 at least, this does not work. |
| 3310 |
// Therefore, do it manually/forcefully IOK 2020-02-17 |
| 3311 |
foreach($shipping_rate->get_meta_data() as $key => $value) { |
| 3312 |
$it->add_meta_data($key,$value,true); |
| 3313 |
} |
| 3314 |
$it->save(); |
| 3315 |
|
| 3316 |
$order->add_item($it); |
| 3317 |
|
| 3318 |
$total_shipping = $it->get_total() ?: 0; |
| 3319 |
$total_shipping_tax = $it->get_total_tax() ?: 0; |
| 3320 |
|
| 3321 |
// Try to avoid calculate_totals, because this will recalculate shipping _without checking if the rate |
| 3322 |
// in question actually should use tax_. Therefore we will just add the pre-calculated values, so that the |
| 3323 |
// value reserved at Vipps and the order total is the same. IOK 2022-10-03 |
| 3324 |
$order->set_shipping_total($total_shipping); |
| 3325 |
$order->set_shipping_tax($total_shipping_tax); |
| 3326 |
|
| 3327 |
$order->set_total($ordertotal + $total_shipping + $total_shipping_tax); |
| 3328 |
$order->update_taxes(); // Necessary for the admin view only; does not recalculate order. |
| 3329 |
|
| 3330 |
// Add an early hook for Vipps Checkout orders with special shipping methods |
| 3331 |
$metadata = $shipping_rate->get_meta_data(); |
| 3332 |
if (isset($metadata['type'])) { |
| 3333 |
do_action('woo_vipps_checkout_special_shipping_method', $order, $shipping_rate, $metadata['type']); |
| 3334 |
} |
| 3335 |
|
| 3336 |
$order->save(); |
| 3337 |
// NB: WE DO NOT CALL CALCULATE TOTALS! |
| 3338 |
// THIS WILL CALCULATE SHIPPING FOR TAX IF THERE IS A TAX RATE FOR THE GIVEN AREA WITH THE 'shipping' PROPERTY CHECKED - EVEN IF THE SHIPPING RATE IT SELF IS NOT THUS CONFIGURED. |
| 3339 |
// Same thing happens when using the "recalculate" button in the backend |
| 3340 |
// This will *only* affect users that choose "No tax" for the shipping rates themselves, which is mostly just wrong, but we want to ensure consistency here since this is hard |
| 3341 |
// to debug. |
| 3342 |
// $order->calculate_totals(true); |
| 3343 |
} |
| 3344 |
|
| 3345 |
|
| 3346 |
// 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. |
| 3347 |
// If possible and safe, user will be logged in before being sent to the thankyou screen. IOK 2020-10-09 |
| 3348 |
// Same thing for Vipps Checkout, mutatis mutandis. The function below returns false if no customer exists or gets created. |
| 3349 |
$customer = false; |
| 3350 |
if ($assigncustomer) { |
| 3351 |
$customer = Vipps::instance()->express_checkout_get_vipps_customer($order); |
| 3352 |
} |
| 3353 |
if ($customer) { |
| 3354 |
// 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 |
| 3355 |
// the same as the 'sub' we get in Login so that must be a future feature. IOK 2020-10-09 |
| 3356 |
// 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 |
| 3357 |
// after calling get_userinfo, so we'll leave that be. |
| 3358 |
if (class_exists('VippsWooLogin') && $customer && !is_wp_error($customer) && !get_user_meta($customer->get_id(), '_vipps_phone',true)) { |
| 3359 |
update_user_meta($customer->get_id(), '_vipps_phone', $billing['phoneNumber']); |
| 3360 |
if (isset($user['sub'])) { |
| 3361 |
$userid = $customer->get_id(); |
| 3362 |
update_user_meta($userid, '_vipps_id', $user['sub']); |
| 3363 |
update_user_meta($userid, '_vipps_just_connected', 1); |
| 3364 |
} |
| 3365 |
} |
| 3366 |
|
| 3367 |
// Ensure we get any changes made to the order, as it will be re-saved later |
| 3368 |
$order = wc_get_order($order->get_id()); |
| 3369 |
} |
| 3370 |
|
| 3371 |
do_action('woo_vipps_set_order_shipping_details', $order, $shipping, $user); |
| 3372 |
$order->save(); // I'm not sure why this is neccessary - but be sure. |
| 3373 |
|
| 3374 |
} |
| 3375 |
|
| 3376 |
// Previously, shipping rates were added by creating them here with metadata packed into the shippingMethodId. This is from 1.4.0 only |
| 3377 |
// used when the woo_vipps_shipping_methods filter has been overriden by the merchant. IOK 2020-02-14 |
| 3378 |
private function get_legacy_express_checkout_shipping_rate($shipping) { |
| 3379 |
$method = $shipping['shippingMethodId']; |
| 3380 |
list ($rate,$tax) = explode(";",$method); |
| 3381 |
// The method ID is encoded in the rate ID but we apparently must still send it to the WC_Shipping_Rate constructor. IOK 2018-06-01 |
| 3382 |
// Unfortunately, Vipps won't accept long enought 'shipingMethodId' for us to actually stash all the information we need. IOK 2018-06-01 |
| 3383 |
list ($method,$product) = explode(":",$rate); |
| 3384 |
$tax = wc_format_decimal($tax,''); |
| 3385 |
$label = $shipping['shippingMethod']; |
| 3386 |
$cost = wc_format_decimal($shipping['shippingCost'],''); // This is inclusive of tax |
| 3387 |
$costExTax= wc_format_decimal($cost-$tax,''); |
| 3388 |
$shipping_rate = new WC_Shipping_Rate($rate,$label,$costExTax,array(array('total'=>$tax)), $method, $product); |
| 3389 |
$shipping_rate = apply_filters('woo_vipps_express_checkout_shipping_rate',$shipping_rate,$costExTax,$tax,$method,$product); |
| 3390 |
return $shipping_rate; |
| 3391 |
} |
| 3392 |
|
| 3393 |
// Used by both callback_check_order_status and handle_callback - sets the neccessary order metadata after a successful (or not vipps transaction). IOK 2025-08-13 |
| 3394 |
public function order_set_transaction_metadata($order, $transaction) { |
| 3395 |
// Set Vipps metadata as early as possible |
| 3396 |
$vippsstamp = strtotime($transaction['timeStamp']); |
| 3397 |
$vippsamount = $transaction['amount'] ?? ''; |
| 3398 |
$vippscurrency= $transaction['currency'] ?? ''; |
| 3399 |
$vippsstatus = $transaction['status']; |
| 3400 |
|
| 3401 |
$order->update_meta_data('_vipps_callback_timestamp',$vippsstamp); |
| 3402 |
$order->update_meta_data('_vipps_amount',$vippsamount); |
| 3403 |
$order->update_meta_data('_vipps_currency',$vippscurrency); |
| 3404 |
$order->update_meta_data('_vipps_status',$vippsstatus); |
| 3405 |
|
| 3406 |
// Checkout only, modify payment method name if neccessary |
| 3407 |
if ($transaction['paymentmethod'] == 'Card') { |
| 3408 |
if ($order->get_meta('_vipps_checkout')) { |
| 3409 |
$order->set_payment_method_title(sprintf(__('Credit Card / %1$s', 'woo-vipps'), Vipps::CheckoutName())); |
| 3410 |
} |
| 3411 |
} |
| 3412 |
// Checkout only, banktransfers are handled specially so note that |
| 3413 |
if ($transaction['paymentmethod'] == 'BankTransfer') { |
| 3414 |
$order->set_payment_method_title(sprintf(__('Bank Transfer/ %1$s', 'woo-vipps'), Vipps::CheckoutName())); |
| 3415 |
$order->update_meta_data('_vipps_api', 'banktransfer'); |
| 3416 |
} |
| 3417 |
} |
| 3418 |
|
| 3419 |
// Handle the callback from Vipps eCom. |
| 3420 |
public function handle_callback($result, $order, $ischeckout=false, $iswebhook=false) { |
| 3421 |
global $Vipps; |
| 3422 |
|
| 3423 |
$vippsorderid = $result['orderId']; |
| 3424 |
$merchant= $result['merchantSerialNumber']; |
| 3425 |
|
| 3426 |
$keyset = $this->get_keyset(); |
| 3427 |
$me = array_keys($keyset); |
| 3428 |
|
| 3429 |
if (!in_array($merchant, $me)) { |
| 3430 |
$this->log(sprintf(__("%1\$s callback with wrong merchantSerialNumber - might be forged",'woo-vipps'), $this->get_payment_method_name()) . " " . $order->get_id(), 'warning'); |
| 3431 |
return false; |
| 3432 |
} |
| 3433 |
|
| 3434 |
if (!$order) { |
| 3435 |
$this->log(sprintf(__("%1\$s callback for unknown order",'woo-vipps'), $this->get_payment_method_name()) . " " . $order->get_id(), 'warning'); |
| 3436 |
return false; |
| 3437 |
} |
| 3438 |
$orderid = $order->get_id(); |
| 3439 |
// We may need to use poll to get data, depending on the content passed. |
| 3440 |
$express = $order->get_meta('_vipps_express_checkout'); |
| 3441 |
$checkout_session = $order->get_meta('_vipps_checkout_session'); |
| 3442 |
|
| 3443 |
if ($vippsorderid != $order->get_meta('_vipps_orderid')) { |
| 3444 |
$this->log(sprintf(__("Wrong %1\$s Orderid - possibly an attempt to fake a callback ", 'woo-vipps'), Vipps::CompanyName()), 'warning'); |
| 3445 |
clean_post_cache($order->get_id()); |
| 3446 |
exit(); |
| 3447 |
} |
| 3448 |
|
| 3449 |
$errorInfo = $result['errorInfo'] ?? ''; |
| 3450 |
if ($errorInfo) { |
| 3451 |
$this->log(sprintf(__("Message in callback from %1\$s for order",'woo-vipps'), $this->get_payment_method_name()) . ' ' . $orderid . ' ' . $errorInfo['errorMessage'],'error'); |
| 3452 |
$order->add_order_note(sprintf(__("Message from %1\$s: %2\$s",'woo-vipps'), $this->get_payment_method_name(), $errorInfo['errorMessage'])); |
| 3453 |
} |
| 3454 |
|
| 3455 |
// The payment details field is passed in Checkout, not in Express, but none of them are complete, so we fill out the values |
| 3456 |
// depending on which one we are IOK 2025-08-13 |
| 3457 |
$details = []; |
| 3458 |
// Checkout has this as a field, containing *some* of the neccessary data |
| 3459 |
if (isset($result['paymentDetails'])) { |
| 3460 |
// Checkout. The sesssion states are # "SessionCreated" "PaymentInitiated" "SessionExpired" "PaymentSuccessful" "PaymentTerminated" |
| 3461 |
// -- we should only get callbacks for successful sessions actually. |
| 3462 |
$details = $result['paymentDetails']; |
| 3463 |
$result['state'] = $result['sessionState'] == 'PaymentSuccessful' ? 'AUTHORIZED' : ($result['sessionState'] == 'PaymentTerminated' ? 'TERMINATED' : 'CREATED'); |
| 3464 |
$details['state'] = $result['state']; |
| 3465 |
$details['paymentMethod'] = $result['paymentMethod']; |
| 3466 |
} else { |
| 3467 |
// This should be an ecom callback; which we need to add a lot of data for to get a valid "paymentDetails". |
| 3468 |
$details = []; |
| 3469 |
$result['state'] = $result['name']; // The name of the callback - which should be AUTHORIZED, TERMINATED etc |
| 3470 |
$details['state'] = $result['name']; |
| 3471 |
$details['amount'] = $result['amount']; // currency, value |
| 3472 |
$details['paymentMethod'] = 'epayment'; |
| 3473 |
$currency = $details['amount']['currency']; |
| 3474 |
$nothing = [ 'currency' => $currency, 'value' => 0]; |
| 3475 |
} |
| 3476 |
|
| 3477 |
// For both callbacks, set 'aggregate' |
| 3478 |
$currency = $details['amount']['currency']; |
| 3479 |
$nothing = [ 'currency' => $currency, 'value' => 0]; |
| 3480 |
$aggregate = ['authorizedAmount' => $nothing, 'cancelledAmount' => $nothing, 'capturedAmount' => $nothing, 'refundedAmount' => $nothing]; |
| 3481 |
if ($details['state'] == 'AUTHORIZED') { |
| 3482 |
$aggregate['authorizedAmount'] = $details['amount']; |
| 3483 |
} |
| 3484 |
$details['aggregate'] = $aggregate; |
| 3485 |
$result['paymentDetails'] = $details; |
| 3486 |
|
| 3487 |
$result = $this->normalizePaymentDetails($result); |
| 3488 |
$details = $result['paymentDetails']; |
| 3489 |
|
| 3490 |
$vippsstatus = $result['status']; // Will exist now, because of the normalization IOK 2025-08-13 |
| 3491 |
$newstatus = $this->interpret_vipps_order_status($vippsstatus); |
| 3492 |
|
| 3493 |
// Extract order metadata from either Checkout or Epayment - set below IOK 2025-08-13 |
| 3494 |
$transaction = array(); |
| 3495 |
$stamp = ($result['timestamp'] ?? false) ? strtotime($result['timestamp']) : time(); |
| 3496 |
$transaction['timeStamp'] = date('Y-m-d H:i:s', $stamp); |
| 3497 |
$transaction['amount'] = $details['amount']['value']; |
| 3498 |
$transaction['currency'] = $details['amount']['currency']; |
| 3499 |
$transaction['status'] = ($result['state'] ?? $details['state']); |
| 3500 |
$transaction['paymentmethod'] = $details['paymentMethod'] ?? ""; |
| 3501 |
|
| 3502 |
if (!$transaction) { |
| 3503 |
$this->log(sprintf(__("Anomalous callback from %1\$s, handle errors and clean up",'woo-vipps'), $this->get_payment_method_name()),'warning'); |
| 3504 |
clean_post_cache($order->get_id()); |
| 3505 |
return false; |
| 3506 |
} |
| 3507 |
|
| 3508 |
$order->add_order_note(sprintf(__('%1$s callback received','woo-vipps'), $this->get_payment_method_name())); |
| 3509 |
do_action('woo_vipps_callback_received', $order, $result, $transaction); |
| 3510 |
|
| 3511 |
$oldstatus = $order->get_status(); |
| 3512 |
if ($oldstatus != 'pending') { |
| 3513 |
// Actually, we are ok with this order, abort the callback. IOK 2018-05-30 |
| 3514 |
clean_post_cache($order->get_id()); |
| 3515 |
return false; |
| 3516 |
} |
| 3517 |
|
| 3518 |
// If the callback is late, and we have called get order status, and this is in progress, we'll log it and just drop the callback. |
| 3519 |
// We do this because neither Woo nor WP has locking, and it isn't feasible to implement one portably. So this reduces somewhat the likelihood of race conditions |
| 3520 |
// when callbacks happen while we are polling for results. IOK 2018-05-30 |
| 3521 |
if (!$Vipps->lockOrder($order)) { |
| 3522 |
clean_post_cache($order->get_id()); |
| 3523 |
return false; |
| 3524 |
} |
| 3525 |
|
| 3526 |
// Ensure we use the same session as for the original order from here on. IOK 2019-10-21 |
| 3527 |
// IOK 2023-07-18 but because of the race condition issue, we cannot guarantee that any changes |
| 3528 |
// made to the session here will be saved. Sorry. |
| 3529 |
$Vipps->callback_restore_session($orderid); |
| 3530 |
|
| 3531 |
// Set Vipps metadata as early as possible |
| 3532 |
$this->order_set_transaction_metadata($order, $transaction); |
| 3533 |
|
| 3534 |
$this->log(sprintf(__("%1\$s callback: Handling order: ", 'woo-vipps'), Vipps::CompanyName()) . " " . $orderid, 'debug'); |
| 3535 |
|
| 3536 |
|
| 3537 |
// This order is ready to set order shipping details etc for IOK 2025-09-19 |
| 3538 |
$ready = false; |
| 3539 |
if (in_array($newstatus, ['authorized', 'complete'])) { |
| 3540 |
$ready = true; |
| 3541 |
} |
| 3542 |
if ($ready) { |
| 3543 |
// Failsafe for rare bug when using Klarna Checkout with Vipps as an external payment method |
| 3544 |
// IOK 2024-01-09 ensure this is called only when order is complete/authorized |
| 3545 |
$this->reset_erroneous_payment_method($order); |
| 3546 |
} |
| 3547 |
|
| 3548 |
if ($ready && ($express || $ischeckout)) { |
| 3549 |
// For Vipps Checkout version 3 there are no more userDetails, so we will add it, including defaults for anonymous purchases IOK 2023-01-10 |
| 3550 |
// This will also normalize userDetails, adding 'sub' where required and fields for backwards compatibility. 2025-08-12 |
| 3551 |
$result = $this->ensure_userDetails($result, $order); |
| 3552 |
|
| 3553 |
// Some Express Checkout orders aren't really express checkout orders, but normal orders to which we have |
| 3554 |
// added scope name, email, phoneNumber. The reason is that we don't care about the address. But then |
| 3555 |
// we also get no user data in the callback, so we must replace the callback with a user info call. IOK 2023-03-10 |
| 3556 |
// IOK 2025-09-29: This is probably *no longer true* - we now almost certainly *always* get a userDetails field if |
| 3557 |
// we have added a scope of any kind. This is therefore probably dead code. |
| 3558 |
// This being dead code, we'll not try to handle errors gracefully here. IOK 2026-03-18 |
| 3559 |
if (!isset($result['userDetails'])) { |
| 3560 |
// This also calls ensure_userDetails and normalizeShippingDetails - but NB: it could fail, so call only when neccessary. |
| 3561 |
try { |
| 3562 |
$details = $this->get_payment_details($order); |
| 3563 |
$result = $details; |
| 3564 |
} catch (Exception $e) { |
| 3565 |
$this->log(sprintf(__("Could not get payment results for order %1\$s", 'woo-vipps'), $order->get_id())); |
| 3566 |
$this->log($e->getMessage()); |
| 3567 |
} |
| 3568 |
} |
| 3569 |
|
| 3570 |
// 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 |
| 3571 |
$result = $this->normalizeShippingDetails($result, $order); |
| 3572 |
|
| 3573 |
// We should now always have shipping details. |
| 3574 |
if (isset($result['shippingDetails'])) { |
| 3575 |
$billing = isset($result['billingDetails']) ? $result['billingDetails'] : false; |
| 3576 |
$this->set_order_shipping_details($order,$result['shippingDetails'], $result['userDetails'], $billing, $result); |
| 3577 |
} |
| 3578 |
} |
| 3579 |
|
| 3580 |
// the only status we now care about is AUTHORIZED. Previously we had AUTHORISED and RESERVED and RESERVE as well. And SALE. |
| 3581 |
if ($vippsstatus == 'AUTHORIZED') { |
| 3582 |
$this->payment_complete($order); |
| 3583 |
} else if ($vippsstatus == 'SALE') { |
| 3584 |
// Direct capture needs special handling because most of the meta values we use are missing IOK 2019-02-26 |
| 3585 |
// Actually not supported anymore, but keep logic. IOK 2025-08-13 |
| 3586 |
$order->add_order_note(sprintf(__('Payment captured directly at %1$s', 'woo-vipps'), $this->get_payment_method_name())); |
| 3587 |
$order->payment_complete(); |
| 3588 |
$this->update_vipps_payment_details($order); |
| 3589 |
} else { |
| 3590 |
// Not ok status; set to failed/cancelled |
| 3591 |
$order_is_retryable = Vipps::order_is_vipps_retryable($order->get_id()); |
| 3592 |
$status_on_fail = $this->get_option('status_on_fail'); |
| 3593 |
$cancel_on_fail = apply_filters('woo_vipps_cancel_failed_orders', false, $order, $vippsstatus); |
| 3594 |
if ($cancel_on_fail || !$order_is_retryable) { |
| 3595 |
$status_on_fail = 'cancelled'; |
| 3596 |
} |
| 3597 |
if (!in_array($status_on_fail, ['cancelled', 'failed'])) { |
| 3598 |
/* translators: order status name. Cancelled is woocommerce status name */ |
| 3599 |
$this->log(__('Unsupported status for payment failure of \'%1$s\', falling back to cancelled.', 'woo-vipps'), 'warning'); |
| 3600 |
$status_on_fail = 'cancelled'; |
| 3601 |
} |
| 3602 |
|
| 3603 |
/* translators: company name */ |
| 3604 |
$order->update_status($status_on_fail, sprintf(__('Callback: Payment cancelled at %1$s', 'woo-vipps'), Vipps::CompanyName())); |
| 3605 |
} |
| 3606 |
|
| 3607 |
$order->save(); |
| 3608 |
clean_post_cache($order->get_id()); |
| 3609 |
|
| 3610 |
// Restore the session again so that we aren't causing issues with the customer-return branch, which may have to update the session concurrently. IOK 2023-018 |
| 3611 |
$Vipps->callback_restore_session($orderid); |
| 3612 |
$Vipps->unlockOrder($order); |
| 3613 |
|
| 3614 |
// Create a signal file (if possible) so the confirm screen knows to check status IOK 2018-05-04 |
| 3615 |
try { |
| 3616 |
$Vipps->createCallbackSignal($order,'ok'); |
| 3617 |
} catch (Exception $e) { |
| 3618 |
// Could not create a signal file, but that's ok. |
| 3619 |
} |
| 3620 |
|
| 3621 |
// Signal that we in fact handled the order. |
| 3622 |
return true; |
| 3623 |
} |
| 3624 |
|
| 3625 |
// Do the 'payment_complete' logic for non-SALE orders IOK 2020-09-22 |
| 3626 |
public function payment_complete($order,$transactionid='') { |
| 3627 |
// Orders not needing processing can be autocaptured, so try to do so now. |
| 3628 |
$autocapture = $this->maybe_complete_payment($order); |
| 3629 |
if (!$autocapture) { |
| 3630 |
add_filter('woocommerce_payment_complete_order_status', |
| 3631 |
function ($status, $orderid, $order) { |
| 3632 |
return $this->after_vipps_order_status($order); |
| 3633 |
}, 99, 3); |
| 3634 |
$order->add_order_note(sprintf(__('Payment authorized at %1$s', 'woo-vipps'), $this->get_payment_method_name())); |
| 3635 |
} |
| 3636 |
$order->payment_complete(); |
| 3637 |
} |
| 3638 |
|
| 3639 |
// Hook run by Woo after order is complete (authorized or sale). We'll add receipt info etc here. |
| 3640 |
public function order_payment_complete ($orderid) { |
| 3641 |
$order = wc_get_order($orderid); |
| 3642 |
if (!is_a($order, 'WC_Order')) return false; |
| 3643 |
if (! Vipps::is_vipps_order($order)) return false; |
| 3644 |
|
| 3645 |
$do_order_management = apply_filters('woo_vipps_order_management_on_payment_complete', true, $orderid); |
| 3646 |
if (!$do_order_management) return; |
| 3647 |
|
| 3648 |
// We do the actual order management call in a separate request which we call non-blocking to avoid it locking |
| 3649 |
// up the users' session on return from the store. This won't work if your Wordpress doesn't support non-blocking calls, |
| 3650 |
// so in this case, allow the user to set a longer timeout (5 seconds should be plenty.) IOK 2022-07-01 |
| 3651 |
// Yes, it will block until timeout even if non-blocking is set. IOK 2022-07-01 |
| 3652 |
$timeout = apply_filters('woo_vipps_asynch_timeout', 0.5); |
| 3653 |
$data = [ 'action'=> 'woo_vipps_order_management','orderid'=>$orderid, 'orderkey' => $order->get_order_key() ]; |
| 3654 |
$args = array( "method" => "POST", "body"=>$data, "timeout"=>$timeout, "blocking" => false); |
| 3655 |
$url = admin_url('admin-post.php'); |
| 3656 |
$asynch = wp_remote_request($url,$args); |
| 3657 |
if (is_wp_error($asynch)) { |
| 3658 |
$this->log(__("Error calling the Order Management API: %1\$s ", 'woo-vipps'), $asynch->get_error_message()); |
| 3659 |
} |
| 3660 |
} |
| 3661 |
|
| 3662 |
// The below actions *may* be long-running, and we can't be sure |
| 3663 |
// they will happen at callback-time. Some user may be affected, even if using wp-cron. |
| 3664 |
// We therefore set them up to be run at shutdown, as soon as payment complete is done |
| 3665 |
public function payment_complete_at_shutdown ($orderid, $orderkey) { |
| 3666 |
// Ensure consistent environment here |
| 3667 |
global $Vipps; |
| 3668 |
if (!$Vipps) $Vipps = Vipps::instance(); |
| 3669 |
$order = wc_get_order($orderid); |
| 3670 |
if (!is_a($order, 'WC_Order')) { |
| 3671 |
return false; |
| 3672 |
} |
| 3673 |
if (! Vipps::is_vipps_order($order)) return false; |
| 3674 |
if ($order->get_order_key() != wc_clean($orderkey)) { |
| 3675 |
return false; |
| 3676 |
} |
| 3677 |
try { |
| 3678 |
|
| 3679 |
$sendreceipt = apply_filters('woo_vipps_send_receipt', ($this->get_option('sendreceipts') == 'yes'), $order); |
| 3680 |
if ($sendreceipt) { |
| 3681 |
$this->api->add_receipt($order); |
| 3682 |
$this->order_add_vipps_categories($order); |
| 3683 |
} |
| 3684 |
do_action('woo_vipps_payment_complete_at_shutdown', $order, $this); |
| 3685 |
} catch (Exception $e) { |
| 3686 |
// This is/should be non-critical so just log it. |
| 3687 |
$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"); |
| 3688 |
} |
| 3689 |
} |
| 3690 |
|
| 3691 |
// This is run on payment complete. Per default will it only add a link to the order confirmation page, but |
| 3692 |
// the hook added should be used if selling tickets, bookings etc to add these to the Vipps app receipt; with images if desired (eg. QR images). |
| 3693 |
public function order_add_vipps_categories ($order) { |
| 3694 |
if (!is_a($order, 'WC_Order')) return; |
| 3695 |
|
| 3696 |
$none = ['link'=>null, 'image'=>null, 'imagesize'=>null]; |
| 3697 |
$orderconfirmation = ['link' => $this->get_return_url($order), 'image' => null, 'imagesize'=>null]; |
| 3698 |
|
| 3699 |
$receipt_image = $this->get_option('receiptimage'); |
| 3700 |
if ($receipt_image) { |
| 3701 |
$orderconfirmation['image'] = intval($receipt_image); |
| 3702 |
$orderconfirmation['imagesize'] = 'full'; |
| 3703 |
} |
| 3704 |
|
| 3705 |
// Do these in this order, in case we get terminated at some point during processing |
| 3706 |
$default = ['TICKET'=>$none,'ORDER_CONFIRMATION' => $orderconfirmation, 'RECEIPT' => $none,"BOOKING" => $none, "DELIVERY" => $none, "GENERAL" => $none]; |
| 3707 |
$categories = apply_filters('woo_vipps_add_order_categories', $default, $order, $this); |
| 3708 |
|
| 3709 |
foreach ($categories as $category=>$data) { |
| 3710 |
if ($data['link']) { |
| 3711 |
$this->order_add_vipps_category($category, $order, $data['link'], $data['image'], $data['imagesize']); |
| 3712 |
} |
| 3713 |
// Let people handle this in other ways if not using filter above IOK 2022-07-01 |
| 3714 |
do_action('woo_vipps_add_order_category', $category, $order, $this); |
| 3715 |
|
| 3716 |
} |
| 3717 |
} |
| 3718 |
|
| 3719 |
// Use the Order Management API to add a link with a category name and an optional image, which is viewable in the order. |
| 3720 |
public function order_add_vipps_category($categoryname, $order, $link, $image=null, $imagesize='medium') { |
| 3721 |
$imageid = $image ? $this->add_vipps_image($image, $imagesize) : null; |
| 3722 |
return $this->api->add_category($order, $link, $imageid, $categoryname); |
| 3723 |
} |
| 3724 |
|
| 3725 |
// Use the Order Management API to upload an image that can be attached to the order. |
| 3726 |
public function add_vipps_image ($imagespec, $size = 'medium') { |
| 3727 |
// Imagespec is an attachmend id (of an image) or a filename (to an image), and a size if using an attachment id. |
| 3728 |
$filename = null; |
| 3729 |
$imageid = 0; |
| 3730 |
$imagefile = ""; |
| 3731 |
$mime = ""; |
| 3732 |
$accepted_types = ["image/jpeg", "image/png", "image/jpg"]; // Only accepted types at Vipps |
| 3733 |
$vippsid = null; |
| 3734 |
$uploads = wp_get_upload_dir(); |
| 3735 |
|
| 3736 |
if (is_numeric($imagespec)) { |
| 3737 |
// Don't send same image twice if we have an id |
| 3738 |
$stored = get_post_meta($imagespec, '_vipps_imageid', true); |
| 3739 |
if ($stored && !$this->is_test_mode()) { |
| 3740 |
return $stored; |
| 3741 |
} |
| 3742 |
$imageid = intval($imagespec); |
| 3743 |
$imagefile = get_attached_file($imageid); |
| 3744 |
$mime = get_post_mime_type($imageid); |
| 3745 |
} |
| 3746 |
|
| 3747 |
if (!is_file($imagefile) || !in_array($mime, $accepted_types)) { |
| 3748 |
$this->log(sprintf(__('%1$s is not an image that can be uploaded to %2$s', 'woo-vipps'), $imagefile, Vipps::CompanyName()), 'error'); |
| 3749 |
$this->log(sprintf(__('File type was %1$s; supported types are %2$s', 'woo-vipps'), $mime, join(", ", $accepted_types)), 'error'); |
| 3750 |
return null; |
| 3751 |
} |
| 3752 |
|
| 3753 |
if ($imageid) { |
| 3754 |
$intermediate = image_get_intermediate_size($imageid, $size); |
| 3755 |
if ($intermediate && isset($intermediate['path'])) { |
| 3756 |
$imagefile = join(DIRECTORY_SEPARATOR, [$uploads['basedir'] , $intermediate['path']]); |
| 3757 |
} |
| 3758 |
} |
| 3759 |
|
| 3760 |
if ($imagefile) { |
| 3761 |
// Check image dimensions before uploading |
| 3762 |
$dimensions = getimagesize($imagefile); |
| 3763 |
if ($dimensions && $dimensions[1] < 167) { // [1] is height |
| 3764 |
$this->log(sprintf(__('Image %1$s is too small - height %2$dpx (minimum 167px required)', 'woo-vipps'), |
| 3765 |
$imagefile, $dimensions[1]), 'error'); |
| 3766 |
return null; |
| 3767 |
} |
| 3768 |
|
| 3769 |
$vippsid = $this->api->add_image($imagefile); |
| 3770 |
if ($vippsid) { |
| 3771 |
update_post_meta($imageid, '_vipps_imageid', $vippsid); |
| 3772 |
} |
| 3773 |
} |
| 3774 |
return $vippsid; |
| 3775 |
} |
| 3776 |
|
| 3777 |
// For the express checkout mechanism, create a partial order without shipping details by simulating checkout->create_order(); |
| 3778 |
// IOK 2018-05-25 |
| 3779 |
public function create_partial_order($ischeckout=false) { |
| 3780 |
// This is neccessary for some plugins, like Yith Dynamic Pricing, that adds filters to get_price depending on whether or not ischeckout is true. |
| 3781 |
// so basically, since we are impersonating WC_Checkout here, we should define this constant too. IOK 2020-07-03 |
| 3782 |
wc_maybe_define_constant('WOOCOMMERCE_CHECKOUT', true ); |
| 3783 |
|
| 3784 |
// In *some* cases you may need to actually load classes and reload the cart, because some plugins do not load when DOING_AJAX. |
| 3785 |
do_action('woo_vipps_express_checkout_before_calculate_totals'); |
| 3786 |
WC()->cart->calculate_fees(); |
| 3787 |
WC()->cart->calculate_totals(); |
| 3788 |
do_action('woo_vipps_before_create_express_checkout_order', WC()->cart); |
| 3789 |
|
| 3790 |
// We store this in the order so we don't have to access the cart when initiating payment. This allows us to restart orders etc. |
| 3791 |
$needs_shipping = WC()->cart->needs_shipping(); |
| 3792 |
|
| 3793 |
$contents = WC()->cart->get_cart_contents(); |
| 3794 |
$contents = apply_filters('woo_vipps_create_express_checkout_cart_contents',$contents); |
| 3795 |
try { |
| 3796 |
$cart_hash = md5(json_encode(wc_clean($contents)) . WC()->cart->total); |
| 3797 |
$order = new WC_Order(); |
| 3798 |
$order->set_status('pending'); |
| 3799 |
$order->set_payment_method($this); |
| 3800 |
if ($ischeckout) { |
| 3801 |
$order->update_meta_data('_vipps_api', 'epayment'); |
| 3802 |
$order->set_payment_method_title('Vipps Checkout'); |
| 3803 |
} else { |
| 3804 |
$order->set_payment_method_title('Vipps Express Checkout'); |
| 3805 |
} |
| 3806 |
// We use 'checkout' as the created_via key as per requests, but allow merchants to use their own. IOK 2022-09-15 |
| 3807 |
$created_via = apply_filters('woo_vipps_express_checkout_created_via', 'checkout', $order, $ischeckout); |
| 3808 |
$order->set_created_via($created_via); |
| 3809 |
|
| 3810 |
$dummy = sprintf(__('Vipps Express Checkout', 'woo-vipps')); // this is so gettext will find this string. |
| 3811 |
$dummy = sprintf(__('Vipps Checkout', 'woo-vipps')); // this is so gettext will find this string. |
| 3812 |
|
| 3813 |
$order->update_meta_data('_vipps_express_checkout',1); |
| 3814 |
|
| 3815 |
# To help with address fields, scope etc in inititate payment |
| 3816 |
$order->update_meta_data('_vipps_needs_shipping', $needs_shipping); |
| 3817 |
|
| 3818 |
$order->set_customer_id( apply_filters('woocommerce_checkout_customer_id', get_current_user_id() ) ); |
| 3819 |
$order->set_currency( get_woocommerce_currency() ); |
| 3820 |
$order->set_prices_include_tax('yes' === get_option('woocommerce_prices_include_tax') ); |
| 3821 |
$order->set_customer_ip_address( WC_Geolocation::get_ip_address() ); |
| 3822 |
$order->set_customer_user_agent( wc_get_user_agent() ); |
| 3823 |
$order->set_discount_total( WC()->cart->get_discount_total()); |
| 3824 |
$order->set_discount_tax( WC()->cart->get_discount_tax() ); |
| 3825 |
$order->set_cart_tax( WC()->cart->get_cart_contents_tax() + WC()->cart->get_fee_tax() ); |
| 3826 |
|
| 3827 |
// Use these methods directly - they should be safe. |
| 3828 |
WC()->checkout->create_order_line_items( $order, WC()->cart); |
| 3829 |
WC()->checkout->create_order_fee_lines( $order, WC()->cart); |
| 3830 |
WC()->checkout->create_order_tax_lines( $order, WC()->cart); |
| 3831 |
WC()->checkout->create_order_coupon_lines( $order, WC()->cart); |
| 3832 |
do_action('woo_vipps_before_calculate_totals_partial_order', $order); |
| 3833 |
$order->calculate_totals(true); |
| 3834 |
|
| 3835 |
// Added to support third-party plugins that wants to do stuff with the order before it is saved. IOK 2020-07-03 |
| 3836 |
do_action('woocommerce_checkout_create_order', $order, array()); |
| 3837 |
|
| 3838 |
$orderid = $order->save(); |
| 3839 |
|
| 3840 |
do_action('woo_vipps_express_checkout_order_created', $orderid); |
| 3841 |
|
| 3842 |
// Normally done by the WC_Checkout::create_order method, so call it here too. IOK 2018-11-19 |
| 3843 |
do_action('woocommerce_checkout_update_order_meta', $orderid, array()); |
| 3844 |
|
| 3845 |
// It isn't possible to remove the javascript or 'after order notice' actions, because these are added as closures |
| 3846 |
// before anything else is run. But we can disable the hook that saves data. IOK 2024-01-18 |
| 3847 |
if (WC_Gateway_Vipps::instance()->get_option('vippsorderattribution') != 'yes') { |
| 3848 |
remove_all_filters( 'woocommerce_order_save_attribution_data'); |
| 3849 |
} |
| 3850 |
|
| 3851 |
// And another one. IOK 2021-11-24 |
| 3852 |
do_action('woocommerce_checkout_order_created', $order ); |
| 3853 |
} catch ( Exception $e ) { |
| 3854 |
if ( $order && $order instanceof WC_Order ) { |
| 3855 |
$order->get_data_store()->release_held_coupons( $order ); |
| 3856 |
do_action('woocommerce_checkout_order_exception', $order ); |
| 3857 |
} |
| 3858 |
// Any errors gets passed upstream IOK 2021-11-24 |
| 3859 |
throw $e; |
| 3860 |
} |
| 3861 |
return $orderid; |
| 3862 |
} |
| 3863 |
|
| 3864 |
// The order attribution subsystem of Woo requires extra work for our partial orders. IOK 2024-01-09 |
| 3865 |
// The attribution data is added to the order forms with prefixes (after 'order note') and we need to strip the prefix. |
| 3866 |
public function get_order_attribution_data($input_data) { |
| 3867 |
$prefix = (string) apply_filters( 'wc_order_attribution_tracking_field_prefix', 'wc_order_attribution_'); |
| 3868 |
$prefix = trim( $prefix, '_' ) . "_"; |
| 3869 |
$len = strlen($prefix); |
| 3870 |
$params = []; |
| 3871 |
foreach($input_data as $key=>$val) { |
| 3872 |
$found = strpos($key, $prefix); |
| 3873 |
if ($found === 0) { |
| 3874 |
$paramkey = substr($key, $len); |
| 3875 |
$params[$paramkey] = $val; |
| 3876 |
} |
| 3877 |
} |
| 3878 |
return $params; |
| 3879 |
} |
| 3880 |
|
| 3881 |
public function save_session_in_order($order) { |
| 3882 |
// The callbacks from Vipps carry no session cookie, so we must store this in the order and use a special session handler when in a callback. |
| 3883 |
// The Vipps class will restore the session from this on callbacks. |
| 3884 |
// IOK 2019-10-21 |
| 3885 |
$sessioncookie = array(); |
| 3886 |
$sessionhandler = WC()->session; |
| 3887 |
if ($sessionhandler && is_a($sessionhandler, 'WC_Session_Handler')) { |
| 3888 |
// If customer is actually logged in, take note IOK 2019-10-25 |
| 3889 |
WC()->session->set('express_customer_id',get_current_user_id()); |
| 3890 |
WC()->session->save_data(); |
| 3891 |
$sessioncookie=$sessionhandler->get_session_cookie(); |
| 3892 |
} else { |
| 3893 |
// This actually can't happen. IOK 2020-04-08. Branch added for debugging only. |
| 3894 |
} |
| 3895 |
|
| 3896 |
if (!empty($sessioncookie)) { |
| 3897 |
// Customer id, session expiration, session-epiring and cookie-hash is the contents. IOK 2019-10-21 |
| 3898 |
$order->update_meta_data('_vipps_sessiondata',json_encode($sessioncookie)); |
| 3899 |
$order->save(); |
| 3900 |
} |
| 3901 |
|
| 3902 |
} |
| 3903 |
|
| 3904 |
// Using this internally to allow the 'enable' button or not. Checks SSL in addition to currency, |
| 3905 |
// is valid_for_use can in principle run on a http version of the page; we only need to have https accessible for callbacks, |
| 3906 |
// but if so, admin should definitely be HTTPS so we just check that. IOK 2018-06-06 |
| 3907 |
public function can_be_activated () { |
| 3908 |
if (!is_ssl() && !preg_match("!^https!i",home_url())) return false; |
| 3909 |
return true; |
| 3910 |
} |
| 3911 |
|
| 3912 |
// Used by the ajax thing that 'sets activated' - checks that it can be activated and that all keys are present. IOK 2018-06-06 |
| 3913 |
function needs_setup() { |
| 3914 |
if (!$this->can_be_activated()) return true; |
| 3915 |
$required = array('merchantSerialNumber','clientId', 'secret', 'Ocp_Apim_Key_eCommerce'); |
| 3916 |
foreach ($required as $key) { |
| 3917 |
if (!$this->get_option($key)) return true; |
| 3918 |
} |
| 3919 |
return false; |
| 3920 |
} |
| 3921 |
|
| 3922 |
// Not present in WooCommerce until 3.4.0. Should be deleted when required versions are incremented. IOK 2018-10-26 |
| 3923 |
public function update_option( $key, $value = '') { |
| 3924 |
if ( empty( $this->settings ) ) { |
| 3925 |
$this->init_settings(); |
| 3926 |
} |
| 3927 |
$this->settings[ $key ] = $value; |
| 3928 |
return update_option( $this->get_option_key(), apply_filters('woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ), 'yes'); |
| 3929 |
} |
| 3930 |
|
| 3931 |
|
| 3932 |
public function admin_options() { |
| 3933 |
$currency = get_woocommerce_currency(); |
| 3934 |
$payment_method = $this->get_payment_method_name(); |
| 3935 |
|
| 3936 |
if (!$this->can_be_activated()) { |
| 3937 |
$this->update_option('enabled', 'no'); |
| 3938 |
} |
| 3939 |
|
| 3940 |
if ($payment_method != "Vipps"): ?> |
| 3941 |
<style> |
| 3942 |
#woocommerce_vipps_express_options, #woocommerce_vipps_express_options-table { |
| 3943 |
display: none; |
| 3944 |
} |
| 3945 |
</style> |
| 3946 |
<?php endif; ?> |
| 3947 |
|
| 3948 |
<h2 id='vipps-settings-page'><?php echo __(Vipps::CompanyName(),'woo-vipps'); ?> <img style="float:right;max-height:40px;margin-top:-15px" alt="<?php _e($this->title,'woo-vipps'); ?>" src="<?php echo $this->icon; ?>"></h2> |
| 3949 |
<?php $this->display_errors(); ?> |
| 3950 |
|
| 3951 |
<?php |
| 3952 |
|
| 3953 |
|
| 3954 |
if (!$this->payment_method_supports_currency($payment_method, $currency)): |
| 3955 |
?> |
| 3956 |
<div class="inline error"> |
| 3957 |
<p><strong><?php echo sprintf(__('%1$s does not support your currency.', 'woo-vipps'), $payment_method); ?></strong> |
| 3958 |
<br> |
| 3959 |
<?php echo sprintf(__('%1$s supported currencies: %2$s', 'woo-vipps'), $payment_method, implode(", ", $this->get_supported_currencies($payment_method))); ?> |
| 3960 |
</p> |
| 3961 |
</div> |
| 3962 |
<?php endif; ?> |
| 3963 |
|
| 3964 |
<?php if (!is_ssl() && !preg_match("!^https!i",home_url())): ?> |
| 3965 |
<div class="inline error"> |
| 3966 |
<p><strong><?php _e('Gateway disabled', 'woocommerce'); ?></strong>: |
| 3967 |
<?php echo sprintf(__('%1$s requires that your site uses HTTPS.', 'woo-vipps'), $payment_method); ?> |
| 3968 |
</p> |
| 3969 |
</div> |
| 3970 |
<?php endif; ?> |
| 3971 |
|
| 3972 |
<?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 |
| 3973 |
$vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 3974 |
?> |
| 3975 |
|
| 3976 |
<?php /* We will *not* allow vipps checkout to be activated at this point, since the product is no longer sold. IOK 2026-04-30 */ ?> |
| 3977 |
<?php if (!$vipps_checkout_activated): ?> |
| 3978 |
|
| 3979 |
<?php endif; ?> |
| 3980 |
<table class="form-table"> |
| 3981 |
<?php $this->generate_settings_html(); ?> |
| 3982 |
</table> <?php |
| 3983 |
} |
| 3984 |
|
| 3985 |
// Validate/mangle input fields |
| 3986 |
function validate_text_field ($key, $value) { |
| 3987 |
if ($key != 'orderprefix') return parent::validate_text_field($key,$value); |
| 3988 |
$value = preg_replace('![^a-zA-Z0-9]!','',$value); |
| 3989 |
return $value; |
| 3990 |
} |
| 3991 |
function validate_checkbox_field($key,$value) { |
| 3992 |
if ($key == 'testmode' && VIPPS_TEST_MODE) { |
| 3993 |
return "yes"; |
| 3994 |
} else if ($key == 'developermode' && VIPPS_TEST_MODE) { |
| 3995 |
return "yes"; |
| 3996 |
} else if ($key == 'enabled') { |
| 3997 |
if ($value && $this->can_be_activated()) return 'yes'; |
| 3998 |
return "no"; |
| 3999 |
} |
| 4000 |
return parent::validate_checkbox_field($key,$value); |
| 4001 |
} |
| 4002 |
|
| 4003 |
function process_admin_options () { |
| 4004 |
// Handle options updates |
| 4005 |
$saved = parent::process_admin_options(); |
| 4006 |
// We may have changed the number of form fields at this point if dev mode was changed |
| 4007 |
// from off to on,so re-initialize the form fields here. IOK 2019-09-03 |
| 4008 |
$this->init_form_fields(); |
| 4009 |
|
| 4010 |
// Reinitialize keysets in case user added/changed these |
| 4011 |
$this->keyset = null; |
| 4012 |
delete_transient('_vipps_keyset'); |
| 4013 |
$keyset = $this->get_keyset(); |
| 4014 |
|
| 4015 |
// IOK FIXME check if we are called using ajax for this; and if so add the notifications to a list of notifications |
| 4016 |
// instead of doing the adminerr/adminnotify thing. IOK 2024-01-03 |
| 4017 |
|
| 4018 |
list($ok,$msg) = $this->check_connection(); |
| 4019 |
if ($ok) { |
| 4020 |
$this->adminnotify(sprintf(__("Connection to %1\$s is OK", 'woo-vipps'), Vipps::CompanyName())); |
| 4021 |
} else { |
| 4022 |
$this->adminerr(sprintf(__("Could not connect to %1\$s", 'woo-vipps'), Vipps::CompanyName()) . ": $msg"); |
| 4023 |
} |
| 4024 |
|
| 4025 |
if ($ok) { |
| 4026 |
$this->check_webhooks(); |
| 4027 |
// Try to ensure we have webhooks defined for the epayment-api IOK 2023-12-19 |
| 4028 |
$hooks = $this->initialize_webhooks(); |
| 4029 |
} |
| 4030 |
|
| 4031 |
// If enabling this, ensure the page in question exists |
| 4032 |
if ($this->get_option('vipps_checkout_enabled') == 'yes') { |
| 4033 |
update_option('woo_vipps_checkout_activated', true, true); // This must be true here, but still, make sure |
| 4034 |
Vipps::instance()->maybe_create_vipps_pages(); |
| 4035 |
} |
| 4036 |
|
| 4037 |
return $saved; |
| 4038 |
} |
| 4039 |
|
| 4040 |
// Check our stored webhooks for consistency, which means the callback URLs should point to *this* site. If they don't, |
| 4041 |
// delete all the ones pointing wrong. If this returns false, you should reinitialize the webhooks. IOK 2023-12-20 |
| 4042 |
public function check_webhooks () { |
| 4043 |
$local_hooks = get_option('_woo_vipps_webhooks'); |
| 4044 |
if (!$local_hooks) return false; |
| 4045 |
|
| 4046 |
$callback = $this->webhook_callback_url(); |
| 4047 |
$callback_compare = strtok($callback, '?'); |
| 4048 |
$problems = false; |
| 4049 |
|
| 4050 |
// We are going to re-initialize our webhooks after this, but just to ensure we're not keeping any 'stale' hooks, |
| 4051 |
// we'll update the local hooks too. IOK 2025-02-13 |
| 4052 |
$change = false; |
| 4053 |
$msns = array_keys($local_hooks); |
| 4054 |
foreach($msns as $msn) { |
| 4055 |
$hooks = $local_hooks[$msn]; |
| 4056 |
$ids = array_keys($hooks); |
| 4057 |
foreach ($ids as $id) { |
| 4058 |
$hook = $hooks[$id]; |
| 4059 |
$noargs = strtok($hook['url'], '?'); |
| 4060 |
if ($noargs == $callback_compare) continue; // This hook is good, probably, unless somebody has deleted it |
| 4061 |
try { |
| 4062 |
$this->log(sprintf(__("For msn %s we have a webhook %s %s which is pointed the wrong way (%s) for this website", 'woo-vipps'), $msn, $hook['id'], $hook['url'], $callback_compare)); |
| 4063 |
$this->api->delete_webhook($msn, $hook['id']); // This isn't - it's pointed the wrong way, which means we have changed name of the site or something |
| 4064 |
} catch (Exception $e) { |
| 4065 |
$this->log(sprintf(__("Could not delete webhook for this site with url '%2\$s' : %1\$s", 'woo-vipps'), $e->getMessage(), $noargs), 'error'); |
| 4066 |
} |
| 4067 |
unset($hooks[$id]); |
| 4068 |
$change = true; |
| 4069 |
$problems = true; |
| 4070 |
} |
| 4071 |
|
| 4072 |
if ($change) { |
| 4073 |
if (empty($hooks)) { |
| 4074 |
unset($local_hooks[$msn]); |
| 4075 |
} else { |
| 4076 |
$local_hooks[$msn] = $hooks; |
| 4077 |
} |
| 4078 |
} |
| 4079 |
} |
| 4080 |
|
| 4081 |
if ($change) { |
| 4082 |
update_option('_woo_vipps_webhooks', $local_hooks, true); |
| 4083 |
} |
| 4084 |
|
| 4085 |
if ($problems) return false; |
| 4086 |
return true; |
| 4087 |
} |
| 4088 |
|
| 4089 |
// Returns the local webhooks for the given msn. If the url has changed, it will return nothing. IOK 2023-12-19 |
| 4090 |
public function get_local_webhook($msn) { |
| 4091 |
$local_hooks = get_option('_woo_vipps_webhooks'); |
| 4092 |
$hooks = $local_hooks[$msn] ?? []; |
| 4093 |
$callback = $this->webhook_callback_url(); |
| 4094 |
$callback_compare = strtok($callback, '?'); |
| 4095 |
|
| 4096 |
foreach ($hooks as $id=>$hook) { |
| 4097 |
$noargs = strtok($hook['url'], '?'); |
| 4098 |
if ($noargs == $callback_compare) { |
| 4099 |
return $hook; |
| 4100 |
} else { |
| 4101 |
// May want to log this somehow |
| 4102 |
} |
| 4103 |
} |
| 4104 |
return null; |
| 4105 |
} |
| 4106 |
|
| 4107 |
|
| 4108 |
// This is to be used in deactivate/uninstall - it deletes all webhooks for all MSNs for this instance |
| 4109 |
// Unfortunatetly, we can't delete other msn's webhooks or webhooks pointing to other URLs. IOK 2023-12-20 |
| 4110 |
public function delete_all_webhooks() { |
| 4111 |
delete_option('_woo_vipps_webhooks'); |
| 4112 |
$callback = $this->webhook_callback_url(); |
| 4113 |
$comparandum = strtok($callback, '?'); |
| 4114 |
$all_hooks = $this->get_webhooks_from_vipps(); |
| 4115 |
foreach($all_hooks as $msn => $data) { |
| 4116 |
$hooks = $data['webhooks'] ?? []; |
| 4117 |
foreach ($hooks as $hook) { |
| 4118 |
$id = $hook['id']; |
| 4119 |
$url = $hook['url']; |
| 4120 |
$noargs = strtok($url, '?'); |
| 4121 |
if ($noargs != $comparandum) continue; // Some other shops hook, we will ignore it |
| 4122 |
$ok = $this->api->delete_webhook($msn,$id); |
| 4123 |
} |
| 4124 |
} |
| 4125 |
} |
| 4126 |
|
| 4127 |
// This will initalize the webhooks for this instance, for all MSNs that are configured. |
| 4128 |
// Hooks that point to us that we *do not* know the secret for, have to be deleted. |
| 4129 |
public function initialize_webhooks() { |
| 4130 |
// IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. |
| 4131 |
try { |
| 4132 |
return $this->initialize_webhooks_internal(); |
| 4133 |
} catch (Exception $e) { |
| 4134 |
$this->log(sprintf(__("Could not initialize webhooks for this site: %1\$s", 'woo-vipps'), $e->getMessage()), 'error'); |
| 4135 |
return []; |
| 4136 |
} |
| 4137 |
} |
| 4138 |
|
| 4139 |
private function initialize_webhooks_internal () { |
| 4140 |
$local_hooks = get_option('_woo_vipps_webhooks'); |
| 4141 |
$all_hooks = $this->get_webhooks_from_vipps(); |
| 4142 |
$ourselves = $this->webhook_callback_url(); |
| 4143 |
$keysets = $this->get_keyset(); |
| 4144 |
|
| 4145 |
// Ignore any extra arguments |
| 4146 |
$comparandum = strtok($ourselves, '?'); |
| 4147 |
|
| 4148 |
$change = false; |
| 4149 |
|
| 4150 |
// We may need to delete webhooks that have been orphaned. There should be exactly one |
| 4151 |
// for this sites' callback, and we need to know its secret. All others should be deleted. |
| 4152 |
$delenda = []; |
| 4153 |
|
| 4154 |
|
| 4155 |
foreach($all_hooks as $msn => $data) { |
| 4156 |
$hooks = $data['webhooks'] ?? []; |
| 4157 |
$gotit = false; |
| 4158 |
$locals = $local_hooks[$msn] ?? []; |
| 4159 |
|
| 4160 |
foreach ($hooks as $hook) { |
| 4161 |
$id = $hook['id']; |
| 4162 |
$url = $hook['url']; |
| 4163 |
$noargs = strtok($url, '?'); |
| 4164 |
if ($noargs != $comparandum) { |
| 4165 |
continue; // Some other shops hook, we will ignore it |
| 4166 |
} |
| 4167 |
$local = $locals[$id] ?? false; |
| 4168 |
|
| 4169 |
// If we haven't gotten our hook yet, but we have a local hook now that we know a secret for, note it and continue |
| 4170 |
if (!$gotit && $local && isset($local['secret'])) { |
| 4171 |
$gotit = $local; |
| 4172 |
continue; |
| 4173 |
} |
| 4174 |
// Now we have a hook for our own msn and url, but either we don't know the secret or it is a duplicate. It should be deleted. |
| 4175 |
$delenda[] = $id; |
| 4176 |
} |
| 4177 |
|
| 4178 |
// Delete all the webhooks for this msn that we don't want |
| 4179 |
foreach ($delenda as $wrong) { |
| 4180 |
$change = true; |
| 4181 |
$this->api->delete_webhook($msn,$wrong); |
| 4182 |
} |
| 4183 |
|
| 4184 |
if ($gotit) { |
| 4185 |
// Now if we got a hook, then we should *just* remember that for this msn. |
| 4186 |
$local_hooks[$msn] = array($gotit['id'] => $gotit); |
| 4187 |
} else { |
| 4188 |
// If not, we don't have a hook for this msn and site, so we need to (try to) create one |
| 4189 |
// but only if the MSN is registered for the payment gateway "vipps" ! IOK 2024-12-03 |
| 4190 |
$keys = $keysets[$msn] ?? []; |
| 4191 |
$gateway = $keys['gw'] ?? 'vipps'; |
| 4192 |
|
| 4193 |
if ($gateway == 'vipps') { |
| 4194 |
$change = true; |
| 4195 |
$result = $this->api->register_webhook($msn, $ourselves); |
| 4196 |
if ($result) { |
| 4197 |
$local_hooks[$msn] = [$result['id'] => ['id'=>$result['id'], 'url' => $ourselves, 'secret' => $result['secret']]]; |
| 4198 |
} |
| 4199 |
} |
| 4200 |
} |
| 4201 |
} |
| 4202 |
update_option('_woo_vipps_webhooks', $local_hooks, true); |
| 4203 |
|
| 4204 |
|
| 4205 |
if ($change) $all_hooks = $this->get_webhooks_from_vipps(); |
| 4206 |
|
| 4207 |
return $all_hooks; |
| 4208 |
} |
| 4209 |
|
| 4210 |
|
| 4211 |
// Checks connection of the 'main' MSN IOK 2023-12-19 |
| 4212 |
public function check_connection ($msn = null) { |
| 4213 |
if (!$msn) { |
| 4214 |
$msn = $this->get_merchant_serial(); |
| 4215 |
} |
| 4216 |
$at = $this->get_key($msn); |
| 4217 |
$s = $this->get_secret($msn); |
| 4218 |
$c = $this->get_clientid($msn); |
| 4219 |
if ($at && $s && $c) { |
| 4220 |
|
| 4221 |
|
| 4222 |
|
| 4223 |
try { |
| 4224 |
// First, test the client id / client secret which will give us an access token |
| 4225 |
$token = $this->api->get_access_token($msn,'force'); |
| 4226 |
if ($token) { |
| 4227 |
// Then, call the webhooks api to check if the msn/sub key is ok |
| 4228 |
try { |
| 4229 |
$this->api->get_webhooks_raw($msn); |
| 4230 |
update_option('woo-vipps-configured', 1, true); |
| 4231 |
return array(true,''); |
| 4232 |
} catch (Exception $e) { |
| 4233 |
$msg = $e->getMessage(); |
| 4234 |
if ($msg == "403 Forbidden") { |
| 4235 |
$msg= __("MSN or subscription key (or both) seem to be wrong: ", 'woo-vipps') . $msg; |
| 4236 |
} |
| 4237 |
update_option('woo-vipps-configured', 0, true); |
| 4238 |
return array(false, $msg); |
| 4239 |
} |
| 4240 |
} |
| 4241 |
|
| 4242 |
} catch (Exception $e) { |
| 4243 |
$msg = __("Your client key or secret is wrong.", 'woo-vipps'); |
| 4244 |
update_option('woo-vipps-configured', 0, true); |
| 4245 |
return array(false, $msg); |
| 4246 |
} |
| 4247 |
} |
| 4248 |
return array(false, ''); // No configuration |
| 4249 |
} |
| 4250 |
|
| 4251 |
public function log ($what,$type='info') { |
| 4252 |
$logger = function_exists('wc_get_logger') ? wc_get_logger() : false; |
| 4253 |
if ($logger) { |
| 4254 |
$context = array('source'=>'woo-vipps'); |
| 4255 |
$logger->log($type,$what,$context); |
| 4256 |
} else { |
| 4257 |
error_log("woo-vipps ($type): $what"); |
| 4258 |
} |
| 4259 |
} |
| 4260 |
|
| 4261 |
// Ensure chosen name gets used in the checkout page IOK 2018-09-12 |
| 4262 |
public function get_title() { |
| 4263 |
return apply_filters('woo_vipps_payment_method_title', $this->get_payment_method_name()); |
| 4264 |
} |
| 4265 |
|
| 4266 |
public function get_payment_method_name() { |
| 4267 |
return $this->get_option('payment_method_name'); |
| 4268 |
} |
| 4269 |
|
| 4270 |
public function payment_fields() { |
| 4271 |
// Use Billing Phone if it is required, otherwise ask for a phone IOK 2018-04-24 |
| 4272 |
// For v2 of the api, just let Vipps ask for then umber |
| 4273 |
// IOK 2019-09-12 removed dead code only used for v1 of api |
| 4274 |
// This just prints a description of the payment method. |
| 4275 |
print $this->get_option('description'); |
| 4276 |
return; |
| 4277 |
} |
| 4278 |
public function validate_fields() { |
| 4279 |
return true; |
| 4280 |
} |
| 4281 |
|
| 4282 |
|
| 4283 |
} |
| 4284 |
|
| 4285 |
|